Fix double-clicks for copying in Edit Position mode
[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             }
1744         }
1745         if (initialMode == AnalyzeMode) {
1746           if (appData.noChessProgram) {
1747             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1748             return;
1749           }
1750           if (appData.icsActive) {
1751             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1752             return;
1753           }
1754           AnalyzeModeEvent();
1755         } else if (initialMode == AnalyzeFile) {
1756           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1757           ShowThinkingEvent();
1758           AnalyzeFileEvent();
1759           AnalysisPeriodicEvent(1);
1760         } else if (initialMode == MachinePlaysWhite) {
1761           if (appData.noChessProgram) {
1762             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1763                               0, 2);
1764             return;
1765           }
1766           if (appData.icsActive) {
1767             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1768                               0, 2);
1769             return;
1770           }
1771           MachineWhiteEvent();
1772         } else if (initialMode == MachinePlaysBlack) {
1773           if (appData.noChessProgram) {
1774             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1775                               0, 2);
1776             return;
1777           }
1778           if (appData.icsActive) {
1779             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1780                               0, 2);
1781             return;
1782           }
1783           MachineBlackEvent();
1784         } else if (initialMode == TwoMachinesPlay) {
1785           if (appData.noChessProgram) {
1786             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1787                               0, 2);
1788             return;
1789           }
1790           if (appData.icsActive) {
1791             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1792                               0, 2);
1793             return;
1794           }
1795           TwoMachinesEvent();
1796         } else if (initialMode == EditGame) {
1797           EditGameEvent();
1798         } else if (initialMode == EditPosition) {
1799           EditPositionEvent();
1800         } else if (initialMode == Training) {
1801           if (*appData.loadGameFile == NULLCHAR) {
1802             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1803             return;
1804           }
1805           TrainingEvent();
1806         }
1807     }
1808 }
1809
1810 void
1811 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1812 {
1813     DisplayBook(current+1);
1814
1815     MoveHistorySet( movelist, first, last, current, pvInfoList );
1816
1817     EvalGraphSet( first, last, current, pvInfoList );
1818
1819     MakeEngineOutputTitle();
1820 }
1821
1822 /*
1823  * Establish will establish a contact to a remote host.port.
1824  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1825  *  used to talk to the host.
1826  * Returns 0 if okay, error code if not.
1827  */
1828 int
1829 establish ()
1830 {
1831     char buf[MSG_SIZ];
1832
1833     if (*appData.icsCommPort != NULLCHAR) {
1834         /* Talk to the host through a serial comm port */
1835         return OpenCommPort(appData.icsCommPort, &icsPR);
1836
1837     } else if (*appData.gateway != NULLCHAR) {
1838         if (*appData.remoteShell == NULLCHAR) {
1839             /* Use the rcmd protocol to run telnet program on a gateway host */
1840             snprintf(buf, sizeof(buf), "%s %s %s",
1841                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1842             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1843
1844         } else {
1845             /* Use the rsh program to run telnet program on a gateway host */
1846             if (*appData.remoteUser == NULLCHAR) {
1847                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1848                         appData.gateway, appData.telnetProgram,
1849                         appData.icsHost, appData.icsPort);
1850             } else {
1851                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1852                         appData.remoteShell, appData.gateway,
1853                         appData.remoteUser, appData.telnetProgram,
1854                         appData.icsHost, appData.icsPort);
1855             }
1856             return StartChildProcess(buf, "", &icsPR);
1857
1858         }
1859     } else if (appData.useTelnet) {
1860         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1861
1862     } else {
1863         /* TCP socket interface differs somewhat between
1864            Unix and NT; handle details in the front end.
1865            */
1866         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1867     }
1868 }
1869
1870 void
1871 EscapeExpand (char *p, char *q)
1872 {       // [HGM] initstring: routine to shape up string arguments
1873         while(*p++ = *q++) if(p[-1] == '\\')
1874             switch(*q++) {
1875                 case 'n': p[-1] = '\n'; break;
1876                 case 'r': p[-1] = '\r'; break;
1877                 case 't': p[-1] = '\t'; break;
1878                 case '\\': p[-1] = '\\'; break;
1879                 case 0: *p = 0; return;
1880                 default: p[-1] = q[-1]; break;
1881             }
1882 }
1883
1884 void
1885 show_bytes (FILE *fp, char *buf, int count)
1886 {
1887     while (count--) {
1888         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1889             fprintf(fp, "\\%03o", *buf & 0xff);
1890         } else {
1891             putc(*buf, fp);
1892         }
1893         buf++;
1894     }
1895     fflush(fp);
1896 }
1897
1898 /* Returns an errno value */
1899 int
1900 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1901 {
1902     char buf[8192], *p, *q, *buflim;
1903     int left, newcount, outcount;
1904
1905     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1906         *appData.gateway != NULLCHAR) {
1907         if (appData.debugMode) {
1908             fprintf(debugFP, ">ICS: ");
1909             show_bytes(debugFP, message, count);
1910             fprintf(debugFP, "\n");
1911         }
1912         return OutputToProcess(pr, message, count, outError);
1913     }
1914
1915     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1916     p = message;
1917     q = buf;
1918     left = count;
1919     newcount = 0;
1920     while (left) {
1921         if (q >= buflim) {
1922             if (appData.debugMode) {
1923                 fprintf(debugFP, ">ICS: ");
1924                 show_bytes(debugFP, buf, newcount);
1925                 fprintf(debugFP, "\n");
1926             }
1927             outcount = OutputToProcess(pr, buf, newcount, outError);
1928             if (outcount < newcount) return -1; /* to be sure */
1929             q = buf;
1930             newcount = 0;
1931         }
1932         if (*p == '\n') {
1933             *q++ = '\r';
1934             newcount++;
1935         } else if (((unsigned char) *p) == TN_IAC) {
1936             *q++ = (char) TN_IAC;
1937             newcount ++;
1938         }
1939         *q++ = *p++;
1940         newcount++;
1941         left--;
1942     }
1943     if (appData.debugMode) {
1944         fprintf(debugFP, ">ICS: ");
1945         show_bytes(debugFP, buf, newcount);
1946         fprintf(debugFP, "\n");
1947     }
1948     outcount = OutputToProcess(pr, buf, newcount, outError);
1949     if (outcount < newcount) return -1; /* to be sure */
1950     return count;
1951 }
1952
1953 void
1954 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1955 {
1956     int outError, outCount;
1957     static int gotEof = 0;
1958     static FILE *ini;
1959
1960     /* Pass data read from player on to ICS */
1961     if (count > 0) {
1962         gotEof = 0;
1963         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1964         if (outCount < count) {
1965             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1966         }
1967         if(have_sent_ICS_logon == 2) {
1968           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1969             fprintf(ini, "%s", message);
1970             have_sent_ICS_logon = 3;
1971           } else
1972             have_sent_ICS_logon = 1;
1973         } else if(have_sent_ICS_logon == 3) {
1974             fprintf(ini, "%s", message);
1975             fclose(ini);
1976           have_sent_ICS_logon = 1;
1977         }
1978     } else if (count < 0) {
1979         RemoveInputSource(isr);
1980         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1981     } else if (gotEof++ > 0) {
1982         RemoveInputSource(isr);
1983         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1984     }
1985 }
1986
1987 void
1988 KeepAlive ()
1989 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1990     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1991     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1992     SendToICS("date\n");
1993     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1994 }
1995
1996 /* added routine for printf style output to ics */
1997 void
1998 ics_printf (char *format, ...)
1999 {
2000     char buffer[MSG_SIZ];
2001     va_list args;
2002
2003     va_start(args, format);
2004     vsnprintf(buffer, sizeof(buffer), format, args);
2005     buffer[sizeof(buffer)-1] = '\0';
2006     SendToICS(buffer);
2007     va_end(args);
2008 }
2009
2010 void
2011 SendToICS (char *s)
2012 {
2013     int count, outCount, outError;
2014
2015     if (icsPR == NoProc) return;
2016
2017     count = strlen(s);
2018     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2019     if (outCount < count) {
2020         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2021     }
2022 }
2023
2024 /* This is used for sending logon scripts to the ICS. Sending
2025    without a delay causes problems when using timestamp on ICC
2026    (at least on my machine). */
2027 void
2028 SendToICSDelayed (char *s, long msdelay)
2029 {
2030     int count, outCount, outError;
2031
2032     if (icsPR == NoProc) return;
2033
2034     count = strlen(s);
2035     if (appData.debugMode) {
2036         fprintf(debugFP, ">ICS: ");
2037         show_bytes(debugFP, s, count);
2038         fprintf(debugFP, "\n");
2039     }
2040     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2041                                       msdelay);
2042     if (outCount < count) {
2043         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2044     }
2045 }
2046
2047
2048 /* Remove all highlighting escape sequences in s
2049    Also deletes any suffix starting with '('
2050    */
2051 char *
2052 StripHighlightAndTitle (char *s)
2053 {
2054     static char retbuf[MSG_SIZ];
2055     char *p = retbuf;
2056
2057     while (*s != NULLCHAR) {
2058         while (*s == '\033') {
2059             while (*s != NULLCHAR && !isalpha(*s)) s++;
2060             if (*s != NULLCHAR) s++;
2061         }
2062         while (*s != NULLCHAR && *s != '\033') {
2063             if (*s == '(' || *s == '[') {
2064                 *p = NULLCHAR;
2065                 return retbuf;
2066             }
2067             *p++ = *s++;
2068         }
2069     }
2070     *p = NULLCHAR;
2071     return retbuf;
2072 }
2073
2074 /* Remove all highlighting escape sequences in s */
2075 char *
2076 StripHighlight (char *s)
2077 {
2078     static char retbuf[MSG_SIZ];
2079     char *p = retbuf;
2080
2081     while (*s != NULLCHAR) {
2082         while (*s == '\033') {
2083             while (*s != NULLCHAR && !isalpha(*s)) s++;
2084             if (*s != NULLCHAR) s++;
2085         }
2086         while (*s != NULLCHAR && *s != '\033') {
2087             *p++ = *s++;
2088         }
2089     }
2090     *p = NULLCHAR;
2091     return retbuf;
2092 }
2093
2094 char engineVariant[MSG_SIZ];
2095 char *variantNames[] = VARIANT_NAMES;
2096 char *
2097 VariantName (VariantClass v)
2098 {
2099     if(v == VariantUnknown || *engineVariant) return engineVariant;
2100     return variantNames[v];
2101 }
2102
2103
2104 /* Identify a variant from the strings the chess servers use or the
2105    PGN Variant tag names we use. */
2106 VariantClass
2107 StringToVariant (char *e)
2108 {
2109     char *p;
2110     int wnum = -1;
2111     VariantClass v = VariantNormal;
2112     int i, found = FALSE;
2113     char buf[MSG_SIZ], c;
2114     int len;
2115
2116     if (!e) return v;
2117
2118     /* [HGM] skip over optional board-size prefixes */
2119     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2120         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2121         while( *e++ != '_');
2122     }
2123
2124     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2125         v = VariantNormal;
2126         found = TRUE;
2127     } else
2128     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2129       if (p = StrCaseStr(e, variantNames[i])) {
2130         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2131         v = (VariantClass) i;
2132         found = TRUE;
2133         break;
2134       }
2135     }
2136
2137     if (!found) {
2138       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2139           || StrCaseStr(e, "wild/fr")
2140           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2141         v = VariantFischeRandom;
2142       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2143                  (i = 1, p = StrCaseStr(e, "w"))) {
2144         p += i;
2145         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2146         if (isdigit(*p)) {
2147           wnum = atoi(p);
2148         } else {
2149           wnum = -1;
2150         }
2151         switch (wnum) {
2152         case 0: /* FICS only, actually */
2153         case 1:
2154           /* Castling legal even if K starts on d-file */
2155           v = VariantWildCastle;
2156           break;
2157         case 2:
2158         case 3:
2159         case 4:
2160           /* Castling illegal even if K & R happen to start in
2161              normal positions. */
2162           v = VariantNoCastle;
2163           break;
2164         case 5:
2165         case 7:
2166         case 8:
2167         case 10:
2168         case 11:
2169         case 12:
2170         case 13:
2171         case 14:
2172         case 15:
2173         case 18:
2174         case 19:
2175           /* Castling legal iff K & R start in normal positions */
2176           v = VariantNormal;
2177           break;
2178         case 6:
2179         case 20:
2180         case 21:
2181           /* Special wilds for position setup; unclear what to do here */
2182           v = VariantLoadable;
2183           break;
2184         case 9:
2185           /* Bizarre ICC game */
2186           v = VariantTwoKings;
2187           break;
2188         case 16:
2189           v = VariantKriegspiel;
2190           break;
2191         case 17:
2192           v = VariantLosers;
2193           break;
2194         case 22:
2195           v = VariantFischeRandom;
2196           break;
2197         case 23:
2198           v = VariantCrazyhouse;
2199           break;
2200         case 24:
2201           v = VariantBughouse;
2202           break;
2203         case 25:
2204           v = Variant3Check;
2205           break;
2206         case 26:
2207           /* Not quite the same as FICS suicide! */
2208           v = VariantGiveaway;
2209           break;
2210         case 27:
2211           v = VariantAtomic;
2212           break;
2213         case 28:
2214           v = VariantShatranj;
2215           break;
2216
2217         /* Temporary names for future ICC types.  The name *will* change in
2218            the next xboard/WinBoard release after ICC defines it. */
2219         case 29:
2220           v = Variant29;
2221           break;
2222         case 30:
2223           v = Variant30;
2224           break;
2225         case 31:
2226           v = Variant31;
2227           break;
2228         case 32:
2229           v = Variant32;
2230           break;
2231         case 33:
2232           v = Variant33;
2233           break;
2234         case 34:
2235           v = Variant34;
2236           break;
2237         case 35:
2238           v = Variant35;
2239           break;
2240         case 36:
2241           v = Variant36;
2242           break;
2243         case 37:
2244           v = VariantShogi;
2245           break;
2246         case 38:
2247           v = VariantXiangqi;
2248           break;
2249         case 39:
2250           v = VariantCourier;
2251           break;
2252         case 40:
2253           v = VariantGothic;
2254           break;
2255         case 41:
2256           v = VariantCapablanca;
2257           break;
2258         case 42:
2259           v = VariantKnightmate;
2260           break;
2261         case 43:
2262           v = VariantFairy;
2263           break;
2264         case 44:
2265           v = VariantCylinder;
2266           break;
2267         case 45:
2268           v = VariantFalcon;
2269           break;
2270         case 46:
2271           v = VariantCapaRandom;
2272           break;
2273         case 47:
2274           v = VariantBerolina;
2275           break;
2276         case 48:
2277           v = VariantJanus;
2278           break;
2279         case 49:
2280           v = VariantSuper;
2281           break;
2282         case 50:
2283           v = VariantGreat;
2284           break;
2285         case -1:
2286           /* Found "wild" or "w" in the string but no number;
2287              must assume it's normal chess. */
2288           v = VariantNormal;
2289           break;
2290         default:
2291           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2292           if( (len >= MSG_SIZ) && appData.debugMode )
2293             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2294
2295           DisplayError(buf, 0);
2296           v = VariantUnknown;
2297           break;
2298         }
2299       }
2300     }
2301     if (appData.debugMode) {
2302       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2303               e, wnum, VariantName(v));
2304     }
2305     return v;
2306 }
2307
2308 static int leftover_start = 0, leftover_len = 0;
2309 char star_match[STAR_MATCH_N][MSG_SIZ];
2310
2311 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2312    advance *index beyond it, and set leftover_start to the new value of
2313    *index; else return FALSE.  If pattern contains the character '*', it
2314    matches any sequence of characters not containing '\r', '\n', or the
2315    character following the '*' (if any), and the matched sequence(s) are
2316    copied into star_match.
2317    */
2318 int
2319 looking_at ( char *buf, int *index, char *pattern)
2320 {
2321     char *bufp = &buf[*index], *patternp = pattern;
2322     int star_count = 0;
2323     char *matchp = star_match[0];
2324
2325     for (;;) {
2326         if (*patternp == NULLCHAR) {
2327             *index = leftover_start = bufp - buf;
2328             *matchp = NULLCHAR;
2329             return TRUE;
2330         }
2331         if (*bufp == NULLCHAR) return FALSE;
2332         if (*patternp == '*') {
2333             if (*bufp == *(patternp + 1)) {
2334                 *matchp = NULLCHAR;
2335                 matchp = star_match[++star_count];
2336                 patternp += 2;
2337                 bufp++;
2338                 continue;
2339             } else if (*bufp == '\n' || *bufp == '\r') {
2340                 patternp++;
2341                 if (*patternp == NULLCHAR)
2342                   continue;
2343                 else
2344                   return FALSE;
2345             } else {
2346                 *matchp++ = *bufp++;
2347                 continue;
2348             }
2349         }
2350         if (*patternp != *bufp) return FALSE;
2351         patternp++;
2352         bufp++;
2353     }
2354 }
2355
2356 void
2357 SendToPlayer (char *data, int length)
2358 {
2359     int error, outCount;
2360     outCount = OutputToProcess(NoProc, data, length, &error);
2361     if (outCount < length) {
2362         DisplayFatalError(_("Error writing to display"), error, 1);
2363     }
2364 }
2365
2366 void
2367 PackHolding (char packed[], char *holding)
2368 {
2369     char *p = holding;
2370     char *q = packed;
2371     int runlength = 0;
2372     int curr = 9999;
2373     do {
2374         if (*p == curr) {
2375             runlength++;
2376         } else {
2377             switch (runlength) {
2378               case 0:
2379                 break;
2380               case 1:
2381                 *q++ = curr;
2382                 break;
2383               case 2:
2384                 *q++ = curr;
2385                 *q++ = curr;
2386                 break;
2387               default:
2388                 sprintf(q, "%d", runlength);
2389                 while (*q) q++;
2390                 *q++ = curr;
2391                 break;
2392             }
2393             runlength = 1;
2394             curr = *p;
2395         }
2396     } while (*p++);
2397     *q = NULLCHAR;
2398 }
2399
2400 /* Telnet protocol requests from the front end */
2401 void
2402 TelnetRequest (unsigned char ddww, unsigned char option)
2403 {
2404     unsigned char msg[3];
2405     int outCount, outError;
2406
2407     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2408
2409     if (appData.debugMode) {
2410         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2411         switch (ddww) {
2412           case TN_DO:
2413             ddwwStr = "DO";
2414             break;
2415           case TN_DONT:
2416             ddwwStr = "DONT";
2417             break;
2418           case TN_WILL:
2419             ddwwStr = "WILL";
2420             break;
2421           case TN_WONT:
2422             ddwwStr = "WONT";
2423             break;
2424           default:
2425             ddwwStr = buf1;
2426             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2427             break;
2428         }
2429         switch (option) {
2430           case TN_ECHO:
2431             optionStr = "ECHO";
2432             break;
2433           default:
2434             optionStr = buf2;
2435             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2436             break;
2437         }
2438         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2439     }
2440     msg[0] = TN_IAC;
2441     msg[1] = ddww;
2442     msg[2] = option;
2443     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2444     if (outCount < 3) {
2445         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2446     }
2447 }
2448
2449 void
2450 DoEcho ()
2451 {
2452     if (!appData.icsActive) return;
2453     TelnetRequest(TN_DO, TN_ECHO);
2454 }
2455
2456 void
2457 DontEcho ()
2458 {
2459     if (!appData.icsActive) return;
2460     TelnetRequest(TN_DONT, TN_ECHO);
2461 }
2462
2463 void
2464 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2465 {
2466     /* put the holdings sent to us by the server on the board holdings area */
2467     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2468     char p;
2469     ChessSquare piece;
2470
2471     if(gameInfo.holdingsWidth < 2)  return;
2472     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2473         return; // prevent overwriting by pre-board holdings
2474
2475     if( (int)lowestPiece >= BlackPawn ) {
2476         holdingsColumn = 0;
2477         countsColumn = 1;
2478         holdingsStartRow = BOARD_HEIGHT-1;
2479         direction = -1;
2480     } else {
2481         holdingsColumn = BOARD_WIDTH-1;
2482         countsColumn = BOARD_WIDTH-2;
2483         holdingsStartRow = 0;
2484         direction = 1;
2485     }
2486
2487     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2488         board[i][holdingsColumn] = EmptySquare;
2489         board[i][countsColumn]   = (ChessSquare) 0;
2490     }
2491     while( (p=*holdings++) != NULLCHAR ) {
2492         piece = CharToPiece( ToUpper(p) );
2493         if(piece == EmptySquare) continue;
2494         /*j = (int) piece - (int) WhitePawn;*/
2495         j = PieceToNumber(piece);
2496         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2497         if(j < 0) continue;               /* should not happen */
2498         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2499         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2500         board[holdingsStartRow+j*direction][countsColumn]++;
2501     }
2502 }
2503
2504
2505 void
2506 VariantSwitch (Board board, VariantClass newVariant)
2507 {
2508    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2509    static Board oldBoard;
2510
2511    startedFromPositionFile = FALSE;
2512    if(gameInfo.variant == newVariant) return;
2513
2514    /* [HGM] This routine is called each time an assignment is made to
2515     * gameInfo.variant during a game, to make sure the board sizes
2516     * are set to match the new variant. If that means adding or deleting
2517     * holdings, we shift the playing board accordingly
2518     * This kludge is needed because in ICS observe mode, we get boards
2519     * of an ongoing game without knowing the variant, and learn about the
2520     * latter only later. This can be because of the move list we requested,
2521     * in which case the game history is refilled from the beginning anyway,
2522     * but also when receiving holdings of a crazyhouse game. In the latter
2523     * case we want to add those holdings to the already received position.
2524     */
2525
2526
2527    if (appData.debugMode) {
2528      fprintf(debugFP, "Switch board from %s to %s\n",
2529              VariantName(gameInfo.variant), VariantName(newVariant));
2530      setbuf(debugFP, NULL);
2531    }
2532    shuffleOpenings = 0;       /* [HGM] shuffle */
2533    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2534    switch(newVariant)
2535      {
2536      case VariantShogi:
2537        newWidth = 9;  newHeight = 9;
2538        gameInfo.holdingsSize = 7;
2539      case VariantBughouse:
2540      case VariantCrazyhouse:
2541        newHoldingsWidth = 2; break;
2542      case VariantGreat:
2543        newWidth = 10;
2544      case VariantSuper:
2545        newHoldingsWidth = 2;
2546        gameInfo.holdingsSize = 8;
2547        break;
2548      case VariantGothic:
2549      case VariantCapablanca:
2550      case VariantCapaRandom:
2551        newWidth = 10;
2552      default:
2553        newHoldingsWidth = gameInfo.holdingsSize = 0;
2554      };
2555
2556    if(newWidth  != gameInfo.boardWidth  ||
2557       newHeight != gameInfo.boardHeight ||
2558       newHoldingsWidth != gameInfo.holdingsWidth ) {
2559
2560      /* shift position to new playing area, if needed */
2561      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2562        for(i=0; i<BOARD_HEIGHT; i++)
2563          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2564            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2565              board[i][j];
2566        for(i=0; i<newHeight; i++) {
2567          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2568          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2569        }
2570      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2571        for(i=0; i<BOARD_HEIGHT; i++)
2572          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2573            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2574              board[i][j];
2575      }
2576      board[HOLDINGS_SET] = 0;
2577      gameInfo.boardWidth  = newWidth;
2578      gameInfo.boardHeight = newHeight;
2579      gameInfo.holdingsWidth = newHoldingsWidth;
2580      gameInfo.variant = newVariant;
2581      InitDrawingSizes(-2, 0);
2582    } else gameInfo.variant = newVariant;
2583    CopyBoard(oldBoard, board);   // remember correctly formatted board
2584      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2585    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2586 }
2587
2588 static int loggedOn = FALSE;
2589
2590 /*-- Game start info cache: --*/
2591 int gs_gamenum;
2592 char gs_kind[MSG_SIZ];
2593 static char player1Name[128] = "";
2594 static char player2Name[128] = "";
2595 static char cont_seq[] = "\n\\   ";
2596 static int player1Rating = -1;
2597 static int player2Rating = -1;
2598 /*----------------------------*/
2599
2600 ColorClass curColor = ColorNormal;
2601 int suppressKibitz = 0;
2602
2603 // [HGM] seekgraph
2604 Boolean soughtPending = FALSE;
2605 Boolean seekGraphUp;
2606 #define MAX_SEEK_ADS 200
2607 #define SQUARE 0x80
2608 char *seekAdList[MAX_SEEK_ADS];
2609 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2610 float tcList[MAX_SEEK_ADS];
2611 char colorList[MAX_SEEK_ADS];
2612 int nrOfSeekAds = 0;
2613 int minRating = 1010, maxRating = 2800;
2614 int hMargin = 10, vMargin = 20, h, w;
2615 extern int squareSize, lineGap;
2616
2617 void
2618 PlotSeekAd (int i)
2619 {
2620         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2621         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2622         if(r < minRating+100 && r >=0 ) r = minRating+100;
2623         if(r > maxRating) r = maxRating;
2624         if(tc < 1.f) tc = 1.f;
2625         if(tc > 95.f) tc = 95.f;
2626         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2627         y = ((double)r - minRating)/(maxRating - minRating)
2628             * (h-vMargin-squareSize/8-1) + vMargin;
2629         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2630         if(strstr(seekAdList[i], " u ")) color = 1;
2631         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2632            !strstr(seekAdList[i], "bullet") &&
2633            !strstr(seekAdList[i], "blitz") &&
2634            !strstr(seekAdList[i], "standard") ) color = 2;
2635         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2636         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2637 }
2638
2639 void
2640 PlotSingleSeekAd (int i)
2641 {
2642         PlotSeekAd(i);
2643 }
2644
2645 void
2646 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2647 {
2648         char buf[MSG_SIZ], *ext = "";
2649         VariantClass v = StringToVariant(type);
2650         if(strstr(type, "wild")) {
2651             ext = type + 4; // append wild number
2652             if(v == VariantFischeRandom) type = "chess960"; else
2653             if(v == VariantLoadable) type = "setup"; else
2654             type = VariantName(v);
2655         }
2656         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2657         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2658             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2659             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2660             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2661             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2662             seekNrList[nrOfSeekAds] = nr;
2663             zList[nrOfSeekAds] = 0;
2664             seekAdList[nrOfSeekAds++] = StrSave(buf);
2665             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2666         }
2667 }
2668
2669 void
2670 EraseSeekDot (int i)
2671 {
2672     int x = xList[i], y = yList[i], d=squareSize/4, k;
2673     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2674     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2675     // now replot every dot that overlapped
2676     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2677         int xx = xList[k], yy = yList[k];
2678         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2679             DrawSeekDot(xx, yy, colorList[k]);
2680     }
2681 }
2682
2683 void
2684 RemoveSeekAd (int nr)
2685 {
2686         int i;
2687         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2688             EraseSeekDot(i);
2689             if(seekAdList[i]) free(seekAdList[i]);
2690             seekAdList[i] = seekAdList[--nrOfSeekAds];
2691             seekNrList[i] = seekNrList[nrOfSeekAds];
2692             ratingList[i] = ratingList[nrOfSeekAds];
2693             colorList[i]  = colorList[nrOfSeekAds];
2694             tcList[i] = tcList[nrOfSeekAds];
2695             xList[i]  = xList[nrOfSeekAds];
2696             yList[i]  = yList[nrOfSeekAds];
2697             zList[i]  = zList[nrOfSeekAds];
2698             seekAdList[nrOfSeekAds] = NULL;
2699             break;
2700         }
2701 }
2702
2703 Boolean
2704 MatchSoughtLine (char *line)
2705 {
2706     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2707     int nr, base, inc, u=0; char dummy;
2708
2709     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2710        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2711        (u=1) &&
2712        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2713         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2714         // match: compact and save the line
2715         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2716         return TRUE;
2717     }
2718     return FALSE;
2719 }
2720
2721 int
2722 DrawSeekGraph ()
2723 {
2724     int i;
2725     if(!seekGraphUp) return FALSE;
2726     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2727     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2728
2729     DrawSeekBackground(0, 0, w, h);
2730     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2731     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2732     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2733         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2734         yy = h-1-yy;
2735         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2736         if(i%500 == 0) {
2737             char buf[MSG_SIZ];
2738             snprintf(buf, MSG_SIZ, "%d", i);
2739             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2740         }
2741     }
2742     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2743     for(i=1; i<100; i+=(i<10?1:5)) {
2744         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2745         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2746         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2747             char buf[MSG_SIZ];
2748             snprintf(buf, MSG_SIZ, "%d", i);
2749             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2750         }
2751     }
2752     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2753     return TRUE;
2754 }
2755
2756 int
2757 SeekGraphClick (ClickType click, int x, int y, int moving)
2758 {
2759     static int lastDown = 0, displayed = 0, lastSecond;
2760     if(y < 0) return FALSE;
2761     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2762         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2763         if(!seekGraphUp) return FALSE;
2764         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2765         DrawPosition(TRUE, NULL);
2766         return TRUE;
2767     }
2768     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2769         if(click == Release || moving) return FALSE;
2770         nrOfSeekAds = 0;
2771         soughtPending = TRUE;
2772         SendToICS(ics_prefix);
2773         SendToICS("sought\n"); // should this be "sought all"?
2774     } else { // issue challenge based on clicked ad
2775         int dist = 10000; int i, closest = 0, second = 0;
2776         for(i=0; i<nrOfSeekAds; i++) {
2777             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2778             if(d < dist) { dist = d; closest = i; }
2779             second += (d - zList[i] < 120); // count in-range ads
2780             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2781         }
2782         if(dist < 120) {
2783             char buf[MSG_SIZ];
2784             second = (second > 1);
2785             if(displayed != closest || second != lastSecond) {
2786                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2787                 lastSecond = second; displayed = closest;
2788             }
2789             if(click == Press) {
2790                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2791                 lastDown = closest;
2792                 return TRUE;
2793             } // on press 'hit', only show info
2794             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2795             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2796             SendToICS(ics_prefix);
2797             SendToICS(buf);
2798             return TRUE; // let incoming board of started game pop down the graph
2799         } else if(click == Release) { // release 'miss' is ignored
2800             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2801             if(moving == 2) { // right up-click
2802                 nrOfSeekAds = 0; // refresh graph
2803                 soughtPending = TRUE;
2804                 SendToICS(ics_prefix);
2805                 SendToICS("sought\n"); // should this be "sought all"?
2806             }
2807             return TRUE;
2808         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2809         // press miss or release hit 'pop down' seek graph
2810         seekGraphUp = FALSE;
2811         DrawPosition(TRUE, NULL);
2812     }
2813     return TRUE;
2814 }
2815
2816 void
2817 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2818 {
2819 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2820 #define STARTED_NONE 0
2821 #define STARTED_MOVES 1
2822 #define STARTED_BOARD 2
2823 #define STARTED_OBSERVE 3
2824 #define STARTED_HOLDINGS 4
2825 #define STARTED_CHATTER 5
2826 #define STARTED_COMMENT 6
2827 #define STARTED_MOVES_NOHIDE 7
2828
2829     static int started = STARTED_NONE;
2830     static char parse[20000];
2831     static int parse_pos = 0;
2832     static char buf[BUF_SIZE + 1];
2833     static int firstTime = TRUE, intfSet = FALSE;
2834     static ColorClass prevColor = ColorNormal;
2835     static int savingComment = FALSE;
2836     static int cmatch = 0; // continuation sequence match
2837     char *bp;
2838     char str[MSG_SIZ];
2839     int i, oldi;
2840     int buf_len;
2841     int next_out;
2842     int tkind;
2843     int backup;    /* [DM] For zippy color lines */
2844     char *p;
2845     char talker[MSG_SIZ]; // [HGM] chat
2846     int channel, collective=0;
2847
2848     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2849
2850     if (appData.debugMode) {
2851       if (!error) {
2852         fprintf(debugFP, "<ICS: ");
2853         show_bytes(debugFP, data, count);
2854         fprintf(debugFP, "\n");
2855       }
2856     }
2857
2858     if (appData.debugMode) { int f = forwardMostMove;
2859         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2860                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2861                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2862     }
2863     if (count > 0) {
2864         /* If last read ended with a partial line that we couldn't parse,
2865            prepend it to the new read and try again. */
2866         if (leftover_len > 0) {
2867             for (i=0; i<leftover_len; i++)
2868               buf[i] = buf[leftover_start + i];
2869         }
2870
2871     /* copy new characters into the buffer */
2872     bp = buf + leftover_len;
2873     buf_len=leftover_len;
2874     for (i=0; i<count; i++)
2875     {
2876         // ignore these
2877         if (data[i] == '\r')
2878             continue;
2879
2880         // join lines split by ICS?
2881         if (!appData.noJoin)
2882         {
2883             /*
2884                 Joining just consists of finding matches against the
2885                 continuation sequence, and discarding that sequence
2886                 if found instead of copying it.  So, until a match
2887                 fails, there's nothing to do since it might be the
2888                 complete sequence, and thus, something we don't want
2889                 copied.
2890             */
2891             if (data[i] == cont_seq[cmatch])
2892             {
2893                 cmatch++;
2894                 if (cmatch == strlen(cont_seq))
2895                 {
2896                     cmatch = 0; // complete match.  just reset the counter
2897
2898                     /*
2899                         it's possible for the ICS to not include the space
2900                         at the end of the last word, making our [correct]
2901                         join operation fuse two separate words.  the server
2902                         does this when the space occurs at the width setting.
2903                     */
2904                     if (!buf_len || buf[buf_len-1] != ' ')
2905                     {
2906                         *bp++ = ' ';
2907                         buf_len++;
2908                     }
2909                 }
2910                 continue;
2911             }
2912             else if (cmatch)
2913             {
2914                 /*
2915                     match failed, so we have to copy what matched before
2916                     falling through and copying this character.  In reality,
2917                     this will only ever be just the newline character, but
2918                     it doesn't hurt to be precise.
2919                 */
2920                 strncpy(bp, cont_seq, cmatch);
2921                 bp += cmatch;
2922                 buf_len += cmatch;
2923                 cmatch = 0;
2924             }
2925         }
2926
2927         // copy this char
2928         *bp++ = data[i];
2929         buf_len++;
2930     }
2931
2932         buf[buf_len] = NULLCHAR;
2933 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2934         next_out = 0;
2935         leftover_start = 0;
2936
2937         i = 0;
2938         while (i < buf_len) {
2939             /* Deal with part of the TELNET option negotiation
2940                protocol.  We refuse to do anything beyond the
2941                defaults, except that we allow the WILL ECHO option,
2942                which ICS uses to turn off password echoing when we are
2943                directly connected to it.  We reject this option
2944                if localLineEditing mode is on (always on in xboard)
2945                and we are talking to port 23, which might be a real
2946                telnet server that will try to keep WILL ECHO on permanently.
2947              */
2948             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2949                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2950                 unsigned char option;
2951                 oldi = i;
2952                 switch ((unsigned char) buf[++i]) {
2953                   case TN_WILL:
2954                     if (appData.debugMode)
2955                       fprintf(debugFP, "\n<WILL ");
2956                     switch (option = (unsigned char) buf[++i]) {
2957                       case TN_ECHO:
2958                         if (appData.debugMode)
2959                           fprintf(debugFP, "ECHO ");
2960                         /* Reply only if this is a change, according
2961                            to the protocol rules. */
2962                         if (remoteEchoOption) break;
2963                         if (appData.localLineEditing &&
2964                             atoi(appData.icsPort) == TN_PORT) {
2965                             TelnetRequest(TN_DONT, TN_ECHO);
2966                         } else {
2967                             EchoOff();
2968                             TelnetRequest(TN_DO, TN_ECHO);
2969                             remoteEchoOption = TRUE;
2970                         }
2971                         break;
2972                       default:
2973                         if (appData.debugMode)
2974                           fprintf(debugFP, "%d ", option);
2975                         /* Whatever this is, we don't want it. */
2976                         TelnetRequest(TN_DONT, option);
2977                         break;
2978                     }
2979                     break;
2980                   case TN_WONT:
2981                     if (appData.debugMode)
2982                       fprintf(debugFP, "\n<WONT ");
2983                     switch (option = (unsigned char) buf[++i]) {
2984                       case TN_ECHO:
2985                         if (appData.debugMode)
2986                           fprintf(debugFP, "ECHO ");
2987                         /* Reply only if this is a change, according
2988                            to the protocol rules. */
2989                         if (!remoteEchoOption) break;
2990                         EchoOn();
2991                         TelnetRequest(TN_DONT, TN_ECHO);
2992                         remoteEchoOption = FALSE;
2993                         break;
2994                       default:
2995                         if (appData.debugMode)
2996                           fprintf(debugFP, "%d ", (unsigned char) option);
2997                         /* Whatever this is, it must already be turned
2998                            off, because we never agree to turn on
2999                            anything non-default, so according to the
3000                            protocol rules, we don't reply. */
3001                         break;
3002                     }
3003                     break;
3004                   case TN_DO:
3005                     if (appData.debugMode)
3006                       fprintf(debugFP, "\n<DO ");
3007                     switch (option = (unsigned char) buf[++i]) {
3008                       default:
3009                         /* Whatever this is, we refuse to do it. */
3010                         if (appData.debugMode)
3011                           fprintf(debugFP, "%d ", option);
3012                         TelnetRequest(TN_WONT, option);
3013                         break;
3014                     }
3015                     break;
3016                   case TN_DONT:
3017                     if (appData.debugMode)
3018                       fprintf(debugFP, "\n<DONT ");
3019                     switch (option = (unsigned char) buf[++i]) {
3020                       default:
3021                         if (appData.debugMode)
3022                           fprintf(debugFP, "%d ", option);
3023                         /* Whatever this is, we are already not doing
3024                            it, because we never agree to do anything
3025                            non-default, so according to the protocol
3026                            rules, we don't reply. */
3027                         break;
3028                     }
3029                     break;
3030                   case TN_IAC:
3031                     if (appData.debugMode)
3032                       fprintf(debugFP, "\n<IAC ");
3033                     /* Doubled IAC; pass it through */
3034                     i--;
3035                     break;
3036                   default:
3037                     if (appData.debugMode)
3038                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3039                     /* Drop all other telnet commands on the floor */
3040                     break;
3041                 }
3042                 if (oldi > next_out)
3043                   SendToPlayer(&buf[next_out], oldi - next_out);
3044                 if (++i > next_out)
3045                   next_out = i;
3046                 continue;
3047             }
3048
3049             /* OK, this at least will *usually* work */
3050             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3051                 loggedOn = TRUE;
3052             }
3053
3054             if (loggedOn && !intfSet) {
3055                 if (ics_type == ICS_ICC) {
3056                   snprintf(str, MSG_SIZ,
3057                           "/set-quietly interface %s\n/set-quietly style 12\n",
3058                           programVersion);
3059                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3060                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3061                 } else if (ics_type == ICS_CHESSNET) {
3062                   snprintf(str, MSG_SIZ, "/style 12\n");
3063                 } else {
3064                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3065                   strcat(str, programVersion);
3066                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3067                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3068                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3069 #ifdef WIN32
3070                   strcat(str, "$iset nohighlight 1\n");
3071 #endif
3072                   strcat(str, "$iset lock 1\n$style 12\n");
3073                 }
3074                 SendToICS(str);
3075                 NotifyFrontendLogin();
3076                 intfSet = TRUE;
3077             }
3078
3079             if (started == STARTED_COMMENT) {
3080                 /* Accumulate characters in comment */
3081                 parse[parse_pos++] = buf[i];
3082                 if (buf[i] == '\n') {
3083                     parse[parse_pos] = NULLCHAR;
3084                     if(chattingPartner>=0) {
3085                         char mess[MSG_SIZ];
3086                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3087                         OutputChatMessage(chattingPartner, mess);
3088                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3089                             int p;
3090                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3091                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3092                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3093                                 OutputChatMessage(p, mess);
3094                                 break;
3095                             }
3096                         }
3097                         chattingPartner = -1;
3098                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3099                         collective = 0;
3100                     } else
3101                     if(!suppressKibitz) // [HGM] kibitz
3102                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3103                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3104                         int nrDigit = 0, nrAlph = 0, j;
3105                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3106                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3107                         parse[parse_pos] = NULLCHAR;
3108                         // try to be smart: if it does not look like search info, it should go to
3109                         // ICS interaction window after all, not to engine-output window.
3110                         for(j=0; j<parse_pos; j++) { // count letters and digits
3111                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3112                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3113                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3114                         }
3115                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3116                             int depth=0; float score;
3117                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3118                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3119                                 pvInfoList[forwardMostMove-1].depth = depth;
3120                                 pvInfoList[forwardMostMove-1].score = 100*score;
3121                             }
3122                             OutputKibitz(suppressKibitz, parse);
3123                         } else {
3124                             char tmp[MSG_SIZ];
3125                             if(gameMode == IcsObserving) // restore original ICS messages
3126                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3127                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3128                             else
3129                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3130                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3131                             SendToPlayer(tmp, strlen(tmp));
3132                         }
3133                         next_out = i+1; // [HGM] suppress printing in ICS window
3134                     }
3135                     started = STARTED_NONE;
3136                 } else {
3137                     /* Don't match patterns against characters in comment */
3138                     i++;
3139                     continue;
3140                 }
3141             }
3142             if (started == STARTED_CHATTER) {
3143                 if (buf[i] != '\n') {
3144                     /* Don't match patterns against characters in chatter */
3145                     i++;
3146                     continue;
3147                 }
3148                 started = STARTED_NONE;
3149                 if(suppressKibitz) next_out = i+1;
3150             }
3151
3152             /* Kludge to deal with rcmd protocol */
3153             if (firstTime && looking_at(buf, &i, "\001*")) {
3154                 DisplayFatalError(&buf[1], 0, 1);
3155                 continue;
3156             } else {
3157                 firstTime = FALSE;
3158             }
3159
3160             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3161                 ics_type = ICS_ICC;
3162                 ics_prefix = "/";
3163                 if (appData.debugMode)
3164                   fprintf(debugFP, "ics_type %d\n", ics_type);
3165                 continue;
3166             }
3167             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3168                 ics_type = ICS_FICS;
3169                 ics_prefix = "$";
3170                 if (appData.debugMode)
3171                   fprintf(debugFP, "ics_type %d\n", ics_type);
3172                 continue;
3173             }
3174             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3175                 ics_type = ICS_CHESSNET;
3176                 ics_prefix = "/";
3177                 if (appData.debugMode)
3178                   fprintf(debugFP, "ics_type %d\n", ics_type);
3179                 continue;
3180             }
3181
3182             if (!loggedOn &&
3183                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3184                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3185                  looking_at(buf, &i, "will be \"*\""))) {
3186               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3187               continue;
3188             }
3189
3190             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3191               char buf[MSG_SIZ];
3192               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3193               DisplayIcsInteractionTitle(buf);
3194               have_set_title = TRUE;
3195             }
3196
3197             /* skip finger notes */
3198             if (started == STARTED_NONE &&
3199                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3200                  (buf[i] == '1' && buf[i+1] == '0')) &&
3201                 buf[i+2] == ':' && buf[i+3] == ' ') {
3202               started = STARTED_CHATTER;
3203               i += 3;
3204               continue;
3205             }
3206
3207             oldi = i;
3208             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3209             if(appData.seekGraph) {
3210                 if(soughtPending && MatchSoughtLine(buf+i)) {
3211                     i = strstr(buf+i, "rated") - buf;
3212                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3213                     next_out = leftover_start = i;
3214                     started = STARTED_CHATTER;
3215                     suppressKibitz = TRUE;
3216                     continue;
3217                 }
3218                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3219                         && looking_at(buf, &i, "* ads displayed")) {
3220                     soughtPending = FALSE;
3221                     seekGraphUp = TRUE;
3222                     DrawSeekGraph();
3223                     continue;
3224                 }
3225                 if(appData.autoRefresh) {
3226                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3227                         int s = (ics_type == ICS_ICC); // ICC format differs
3228                         if(seekGraphUp)
3229                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3230                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3231                         looking_at(buf, &i, "*% "); // eat prompt
3232                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3233                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3234                         next_out = i; // suppress
3235                         continue;
3236                     }
3237                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3238                         char *p = star_match[0];
3239                         while(*p) {
3240                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3241                             while(*p && *p++ != ' '); // next
3242                         }
3243                         looking_at(buf, &i, "*% "); // eat prompt
3244                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3245                         next_out = i;
3246                         continue;
3247                     }
3248                 }
3249             }
3250
3251             /* skip formula vars */
3252             if (started == STARTED_NONE &&
3253                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3254               started = STARTED_CHATTER;
3255               i += 3;
3256               continue;
3257             }
3258
3259             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3260             if (appData.autoKibitz && started == STARTED_NONE &&
3261                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3262                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3263                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3264                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3265                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3266                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3267                         suppressKibitz = TRUE;
3268                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3269                         next_out = i;
3270                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3271                                 && (gameMode == IcsPlayingWhite)) ||
3272                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3273                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3274                             started = STARTED_CHATTER; // own kibitz we simply discard
3275                         else {
3276                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3277                             parse_pos = 0; parse[0] = NULLCHAR;
3278                             savingComment = TRUE;
3279                             suppressKibitz = gameMode != IcsObserving ? 2 :
3280                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3281                         }
3282                         continue;
3283                 } else
3284                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3285                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3286                          && atoi(star_match[0])) {
3287                     // suppress the acknowledgements of our own autoKibitz
3288                     char *p;
3289                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3290                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3291                     SendToPlayer(star_match[0], strlen(star_match[0]));
3292                     if(looking_at(buf, &i, "*% ")) // eat prompt
3293                         suppressKibitz = FALSE;
3294                     next_out = i;
3295                     continue;
3296                 }
3297             } // [HGM] kibitz: end of patch
3298
3299             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3300
3301             // [HGM] chat: intercept tells by users for which we have an open chat window
3302             channel = -1;
3303             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3304                                            looking_at(buf, &i, "* whispers:") ||
3305                                            looking_at(buf, &i, "* kibitzes:") ||
3306                                            looking_at(buf, &i, "* shouts:") ||
3307                                            looking_at(buf, &i, "* c-shouts:") ||
3308                                            looking_at(buf, &i, "--> * ") ||
3309                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3310                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3311                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3312                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3313                 int p;
3314                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3315                 chattingPartner = -1; collective = 0;
3316
3317                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3318                 for(p=0; p<MAX_CHAT; p++) {
3319                     collective = 1;
3320                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3321                     talker[0] = '['; strcat(talker, "] ");
3322                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3323                     chattingPartner = p; break;
3324                     }
3325                 } else
3326                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3327                 for(p=0; p<MAX_CHAT; p++) {
3328                     collective = 1;
3329                     if(!strcmp("kibitzes", chatPartner[p])) {
3330                         talker[0] = '['; strcat(talker, "] ");
3331                         chattingPartner = p; break;
3332                     }
3333                 } else
3334                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3335                 for(p=0; p<MAX_CHAT; p++) {
3336                     collective = 1;
3337                     if(!strcmp("whispers", chatPartner[p])) {
3338                         talker[0] = '['; strcat(talker, "] ");
3339                         chattingPartner = p; break;
3340                     }
3341                 } else
3342                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3343                   if(buf[i-8] == '-' && buf[i-3] == 't')
3344                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3345                     collective = 1;
3346                     if(!strcmp("c-shouts", chatPartner[p])) {
3347                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3348                         chattingPartner = p; break;
3349                     }
3350                   }
3351                   if(chattingPartner < 0)
3352                   for(p=0; p<MAX_CHAT; p++) {
3353                     collective = 1;
3354                     if(!strcmp("shouts", chatPartner[p])) {
3355                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3356                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3357                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3358                         chattingPartner = p; break;
3359                     }
3360                   }
3361                 }
3362                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3363                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3364                     talker[0] = 0;
3365                     Colorize(ColorTell, FALSE);
3366                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3367                     collective |= 2;
3368                     chattingPartner = p; break;
3369                 }
3370                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3371                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3372                     started = STARTED_COMMENT;
3373                     parse_pos = 0; parse[0] = NULLCHAR;
3374                     savingComment = 3 + chattingPartner; // counts as TRUE
3375                     if(collective == 3) i = oldi; else {
3376                         suppressKibitz = TRUE;
3377                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3378                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3379                         continue;
3380                     }
3381                 }
3382             } // [HGM] chat: end of patch
3383
3384           backup = i;
3385             if (appData.zippyTalk || appData.zippyPlay) {
3386                 /* [DM] Backup address for color zippy lines */
3387 #if ZIPPY
3388                if (loggedOn == TRUE)
3389                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3390                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3391 #endif
3392             } // [DM] 'else { ' deleted
3393                 if (
3394                     /* Regular tells and says */
3395                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3396                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3397                     looking_at(buf, &i, "* says: ") ||
3398                     /* Don't color "message" or "messages" output */
3399                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3400                     looking_at(buf, &i, "*. * at *:*: ") ||
3401                     looking_at(buf, &i, "--* (*:*): ") ||
3402                     /* Message notifications (same color as tells) */
3403                     looking_at(buf, &i, "* has left a message ") ||
3404                     looking_at(buf, &i, "* just sent you a message:\n") ||
3405                     /* Whispers and kibitzes */
3406                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3407                     looking_at(buf, &i, "* kibitzes: ") ||
3408                     /* Channel tells */
3409                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3410
3411                   if (tkind == 1 && strchr(star_match[0], ':')) {
3412                       /* Avoid "tells you:" spoofs in channels */
3413                      tkind = 3;
3414                   }
3415                   if (star_match[0][0] == NULLCHAR ||
3416                       strchr(star_match[0], ' ') ||
3417                       (tkind == 3 && strchr(star_match[1], ' '))) {
3418                     /* Reject bogus matches */
3419                     i = oldi;
3420                   } else {
3421                     if (appData.colorize) {
3422                       if (oldi > next_out) {
3423                         SendToPlayer(&buf[next_out], oldi - next_out);
3424                         next_out = oldi;
3425                       }
3426                       switch (tkind) {
3427                       case 1:
3428                         Colorize(ColorTell, FALSE);
3429                         curColor = ColorTell;
3430                         break;
3431                       case 2:
3432                         Colorize(ColorKibitz, FALSE);
3433                         curColor = ColorKibitz;
3434                         break;
3435                       case 3:
3436                         p = strrchr(star_match[1], '(');
3437                         if (p == NULL) {
3438                           p = star_match[1];
3439                         } else {
3440                           p++;
3441                         }
3442                         if (atoi(p) == 1) {
3443                           Colorize(ColorChannel1, FALSE);
3444                           curColor = ColorChannel1;
3445                         } else {
3446                           Colorize(ColorChannel, FALSE);
3447                           curColor = ColorChannel;
3448                         }
3449                         break;
3450                       case 5:
3451                         curColor = ColorNormal;
3452                         break;
3453                       }
3454                     }
3455                     if (started == STARTED_NONE && appData.autoComment &&
3456                         (gameMode == IcsObserving ||
3457                          gameMode == IcsPlayingWhite ||
3458                          gameMode == IcsPlayingBlack)) {
3459                       parse_pos = i - oldi;
3460                       memcpy(parse, &buf[oldi], parse_pos);
3461                       parse[parse_pos] = NULLCHAR;
3462                       started = STARTED_COMMENT;
3463                       savingComment = TRUE;
3464                     } else if(collective != 3) {
3465                       started = STARTED_CHATTER;
3466                       savingComment = FALSE;
3467                     }
3468                     loggedOn = TRUE;
3469                     continue;
3470                   }
3471                 }
3472
3473                 if (looking_at(buf, &i, "* s-shouts: ") ||
3474                     looking_at(buf, &i, "* c-shouts: ")) {
3475                     if (appData.colorize) {
3476                         if (oldi > next_out) {
3477                             SendToPlayer(&buf[next_out], oldi - next_out);
3478                             next_out = oldi;
3479                         }
3480                         Colorize(ColorSShout, FALSE);
3481                         curColor = ColorSShout;
3482                     }
3483                     loggedOn = TRUE;
3484                     started = STARTED_CHATTER;
3485                     continue;
3486                 }
3487
3488                 if (looking_at(buf, &i, "--->")) {
3489                     loggedOn = TRUE;
3490                     continue;
3491                 }
3492
3493                 if (looking_at(buf, &i, "* shouts: ") ||
3494                     looking_at(buf, &i, "--> ")) {
3495                     if (appData.colorize) {
3496                         if (oldi > next_out) {
3497                             SendToPlayer(&buf[next_out], oldi - next_out);
3498                             next_out = oldi;
3499                         }
3500                         Colorize(ColorShout, FALSE);
3501                         curColor = ColorShout;
3502                     }
3503                     loggedOn = TRUE;
3504                     started = STARTED_CHATTER;
3505                     continue;
3506                 }
3507
3508                 if (looking_at( buf, &i, "Challenge:")) {
3509                     if (appData.colorize) {
3510                         if (oldi > next_out) {
3511                             SendToPlayer(&buf[next_out], oldi - next_out);
3512                             next_out = oldi;
3513                         }
3514                         Colorize(ColorChallenge, FALSE);
3515                         curColor = ColorChallenge;
3516                     }
3517                     loggedOn = TRUE;
3518                     continue;
3519                 }
3520
3521                 if (looking_at(buf, &i, "* offers you") ||
3522                     looking_at(buf, &i, "* offers to be") ||
3523                     looking_at(buf, &i, "* would like to") ||
3524                     looking_at(buf, &i, "* requests to") ||
3525                     looking_at(buf, &i, "Your opponent offers") ||
3526                     looking_at(buf, &i, "Your opponent requests")) {
3527
3528                     if (appData.colorize) {
3529                         if (oldi > next_out) {
3530                             SendToPlayer(&buf[next_out], oldi - next_out);
3531                             next_out = oldi;
3532                         }
3533                         Colorize(ColorRequest, FALSE);
3534                         curColor = ColorRequest;
3535                     }
3536                     continue;
3537                 }
3538
3539                 if (looking_at(buf, &i, "* (*) seeking")) {
3540                     if (appData.colorize) {
3541                         if (oldi > next_out) {
3542                             SendToPlayer(&buf[next_out], oldi - next_out);
3543                             next_out = oldi;
3544                         }
3545                         Colorize(ColorSeek, FALSE);
3546                         curColor = ColorSeek;
3547                     }
3548                     continue;
3549             }
3550
3551           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3552
3553             if (looking_at(buf, &i, "\\   ")) {
3554                 if (prevColor != ColorNormal) {
3555                     if (oldi > next_out) {
3556                         SendToPlayer(&buf[next_out], oldi - next_out);
3557                         next_out = oldi;
3558                     }
3559                     Colorize(prevColor, TRUE);
3560                     curColor = prevColor;
3561                 }
3562                 if (savingComment) {
3563                     parse_pos = i - oldi;
3564                     memcpy(parse, &buf[oldi], parse_pos);
3565                     parse[parse_pos] = NULLCHAR;
3566                     started = STARTED_COMMENT;
3567                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3568                         chattingPartner = savingComment - 3; // kludge to remember the box
3569                 } else {
3570                     started = STARTED_CHATTER;
3571                 }
3572                 continue;
3573             }
3574
3575             if (looking_at(buf, &i, "Black Strength :") ||
3576                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3577                 looking_at(buf, &i, "<10>") ||
3578                 looking_at(buf, &i, "#@#")) {
3579                 /* Wrong board style */
3580                 loggedOn = TRUE;
3581                 SendToICS(ics_prefix);
3582                 SendToICS("set style 12\n");
3583                 SendToICS(ics_prefix);
3584                 SendToICS("refresh\n");
3585                 continue;
3586             }
3587
3588             if (looking_at(buf, &i, "login:")) {
3589               if (!have_sent_ICS_logon) {
3590                 if(ICSInitScript())
3591                   have_sent_ICS_logon = 1;
3592                 else // no init script was found
3593                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3594               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3595                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3596               }
3597                 continue;
3598             }
3599
3600             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3601                 (looking_at(buf, &i, "\n<12> ") ||
3602                  looking_at(buf, &i, "<12> "))) {
3603                 loggedOn = TRUE;
3604                 if (oldi > next_out) {
3605                     SendToPlayer(&buf[next_out], oldi - next_out);
3606                 }
3607                 next_out = i;
3608                 started = STARTED_BOARD;
3609                 parse_pos = 0;
3610                 continue;
3611             }
3612
3613             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3614                 looking_at(buf, &i, "<b1> ")) {
3615                 if (oldi > next_out) {
3616                     SendToPlayer(&buf[next_out], oldi - next_out);
3617                 }
3618                 next_out = i;
3619                 started = STARTED_HOLDINGS;
3620                 parse_pos = 0;
3621                 continue;
3622             }
3623
3624             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3625                 loggedOn = TRUE;
3626                 /* Header for a move list -- first line */
3627
3628                 switch (ics_getting_history) {
3629                   case H_FALSE:
3630                     switch (gameMode) {
3631                       case IcsIdle:
3632                       case BeginningOfGame:
3633                         /* User typed "moves" or "oldmoves" while we
3634                            were idle.  Pretend we asked for these
3635                            moves and soak them up so user can step
3636                            through them and/or save them.
3637                            */
3638                         Reset(FALSE, TRUE);
3639                         gameMode = IcsObserving;
3640                         ModeHighlight();
3641                         ics_gamenum = -1;
3642                         ics_getting_history = H_GOT_UNREQ_HEADER;
3643                         break;
3644                       case EditGame: /*?*/
3645                       case EditPosition: /*?*/
3646                         /* Should above feature work in these modes too? */
3647                         /* For now it doesn't */
3648                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3649                         break;
3650                       default:
3651                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3652                         break;
3653                     }
3654                     break;
3655                   case H_REQUESTED:
3656                     /* Is this the right one? */
3657                     if (gameInfo.white && gameInfo.black &&
3658                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3659                         strcmp(gameInfo.black, star_match[2]) == 0) {
3660                         /* All is well */
3661                         ics_getting_history = H_GOT_REQ_HEADER;
3662                     }
3663                     break;
3664                   case H_GOT_REQ_HEADER:
3665                   case H_GOT_UNREQ_HEADER:
3666                   case H_GOT_UNWANTED_HEADER:
3667                   case H_GETTING_MOVES:
3668                     /* Should not happen */
3669                     DisplayError(_("Error gathering move list: two headers"), 0);
3670                     ics_getting_history = H_FALSE;
3671                     break;
3672                 }
3673
3674                 /* Save player ratings into gameInfo if needed */
3675                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3676                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3677                     (gameInfo.whiteRating == -1 ||
3678                      gameInfo.blackRating == -1)) {
3679
3680                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3681                     gameInfo.blackRating = string_to_rating(star_match[3]);
3682                     if (appData.debugMode)
3683                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3684                               gameInfo.whiteRating, gameInfo.blackRating);
3685                 }
3686                 continue;
3687             }
3688
3689             if (looking_at(buf, &i,
3690               "* * match, initial time: * minute*, increment: * second")) {
3691                 /* Header for a move list -- second line */
3692                 /* Initial board will follow if this is a wild game */
3693                 if (gameInfo.event != NULL) free(gameInfo.event);
3694                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3695                 gameInfo.event = StrSave(str);
3696                 /* [HGM] we switched variant. Translate boards if needed. */
3697                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3698                 continue;
3699             }
3700
3701             if (looking_at(buf, &i, "Move  ")) {
3702                 /* Beginning of a move list */
3703                 switch (ics_getting_history) {
3704                   case H_FALSE:
3705                     /* Normally should not happen */
3706                     /* Maybe user hit reset while we were parsing */
3707                     break;
3708                   case H_REQUESTED:
3709                     /* Happens if we are ignoring a move list that is not
3710                      * the one we just requested.  Common if the user
3711                      * tries to observe two games without turning off
3712                      * getMoveList */
3713                     break;
3714                   case H_GETTING_MOVES:
3715                     /* Should not happen */
3716                     DisplayError(_("Error gathering move list: nested"), 0);
3717                     ics_getting_history = H_FALSE;
3718                     break;
3719                   case H_GOT_REQ_HEADER:
3720                     ics_getting_history = H_GETTING_MOVES;
3721                     started = STARTED_MOVES;
3722                     parse_pos = 0;
3723                     if (oldi > next_out) {
3724                         SendToPlayer(&buf[next_out], oldi - next_out);
3725                     }
3726                     break;
3727                   case H_GOT_UNREQ_HEADER:
3728                     ics_getting_history = H_GETTING_MOVES;
3729                     started = STARTED_MOVES_NOHIDE;
3730                     parse_pos = 0;
3731                     break;
3732                   case H_GOT_UNWANTED_HEADER:
3733                     ics_getting_history = H_FALSE;
3734                     break;
3735                 }
3736                 continue;
3737             }
3738
3739             if (looking_at(buf, &i, "% ") ||
3740                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3741                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3742                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3743                     soughtPending = FALSE;
3744                     seekGraphUp = TRUE;
3745                     DrawSeekGraph();
3746                 }
3747                 if(suppressKibitz) next_out = i;
3748                 savingComment = FALSE;
3749                 suppressKibitz = 0;
3750                 switch (started) {
3751                   case STARTED_MOVES:
3752                   case STARTED_MOVES_NOHIDE:
3753                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3754                     parse[parse_pos + i - oldi] = NULLCHAR;
3755                     ParseGameHistory(parse);
3756 #if ZIPPY
3757                     if (appData.zippyPlay && first.initDone) {
3758                         FeedMovesToProgram(&first, forwardMostMove);
3759                         if (gameMode == IcsPlayingWhite) {
3760                             if (WhiteOnMove(forwardMostMove)) {
3761                                 if (first.sendTime) {
3762                                   if (first.useColors) {
3763                                     SendToProgram("black\n", &first);
3764                                   }
3765                                   SendTimeRemaining(&first, TRUE);
3766                                 }
3767                                 if (first.useColors) {
3768                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3769                                 }
3770                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3771                                 first.maybeThinking = TRUE;
3772                             } else {
3773                                 if (first.usePlayother) {
3774                                   if (first.sendTime) {
3775                                     SendTimeRemaining(&first, TRUE);
3776                                   }
3777                                   SendToProgram("playother\n", &first);
3778                                   firstMove = FALSE;
3779                                 } else {
3780                                   firstMove = TRUE;
3781                                 }
3782                             }
3783                         } else if (gameMode == IcsPlayingBlack) {
3784                             if (!WhiteOnMove(forwardMostMove)) {
3785                                 if (first.sendTime) {
3786                                   if (first.useColors) {
3787                                     SendToProgram("white\n", &first);
3788                                   }
3789                                   SendTimeRemaining(&first, FALSE);
3790                                 }
3791                                 if (first.useColors) {
3792                                   SendToProgram("black\n", &first);
3793                                 }
3794                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3795                                 first.maybeThinking = TRUE;
3796                             } else {
3797                                 if (first.usePlayother) {
3798                                   if (first.sendTime) {
3799                                     SendTimeRemaining(&first, FALSE);
3800                                   }
3801                                   SendToProgram("playother\n", &first);
3802                                   firstMove = FALSE;
3803                                 } else {
3804                                   firstMove = TRUE;
3805                                 }
3806                             }
3807                         }
3808                     }
3809 #endif
3810                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3811                         /* Moves came from oldmoves or moves command
3812                            while we weren't doing anything else.
3813                            */
3814                         currentMove = forwardMostMove;
3815                         ClearHighlights();/*!!could figure this out*/
3816                         flipView = appData.flipView;
3817                         DrawPosition(TRUE, boards[currentMove]);
3818                         DisplayBothClocks();
3819                         snprintf(str, MSG_SIZ, "%s %s %s",
3820                                 gameInfo.white, _("vs."),  gameInfo.black);
3821                         DisplayTitle(str);
3822                         gameMode = IcsIdle;
3823                     } else {
3824                         /* Moves were history of an active game */
3825                         if (gameInfo.resultDetails != NULL) {
3826                             free(gameInfo.resultDetails);
3827                             gameInfo.resultDetails = NULL;
3828                         }
3829                     }
3830                     HistorySet(parseList, backwardMostMove,
3831                                forwardMostMove, currentMove-1);
3832                     DisplayMove(currentMove - 1);
3833                     if (started == STARTED_MOVES) next_out = i;
3834                     started = STARTED_NONE;
3835                     ics_getting_history = H_FALSE;
3836                     break;
3837
3838                   case STARTED_OBSERVE:
3839                     started = STARTED_NONE;
3840                     SendToICS(ics_prefix);
3841                     SendToICS("refresh\n");
3842                     break;
3843
3844                   default:
3845                     break;
3846                 }
3847                 if(bookHit) { // [HGM] book: simulate book reply
3848                     static char bookMove[MSG_SIZ]; // a bit generous?
3849
3850                     programStats.nodes = programStats.depth = programStats.time =
3851                     programStats.score = programStats.got_only_move = 0;
3852                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3853
3854                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3855                     strcat(bookMove, bookHit);
3856                     HandleMachineMove(bookMove, &first);
3857                 }
3858                 continue;
3859             }
3860
3861             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3862                  started == STARTED_HOLDINGS ||
3863                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3864                 /* Accumulate characters in move list or board */
3865                 parse[parse_pos++] = buf[i];
3866             }
3867
3868             /* Start of game messages.  Mostly we detect start of game
3869                when the first board image arrives.  On some versions
3870                of the ICS, though, we need to do a "refresh" after starting
3871                to observe in order to get the current board right away. */
3872             if (looking_at(buf, &i, "Adding game * to observation list")) {
3873                 started = STARTED_OBSERVE;
3874                 continue;
3875             }
3876
3877             /* Handle auto-observe */
3878             if (appData.autoObserve &&
3879                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3880                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3881                 char *player;
3882                 /* Choose the player that was highlighted, if any. */
3883                 if (star_match[0][0] == '\033' ||
3884                     star_match[1][0] != '\033') {
3885                     player = star_match[0];
3886                 } else {
3887                     player = star_match[2];
3888                 }
3889                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3890                         ics_prefix, StripHighlightAndTitle(player));
3891                 SendToICS(str);
3892
3893                 /* Save ratings from notify string */
3894                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3895                 player1Rating = string_to_rating(star_match[1]);
3896                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3897                 player2Rating = string_to_rating(star_match[3]);
3898
3899                 if (appData.debugMode)
3900                   fprintf(debugFP,
3901                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3902                           player1Name, player1Rating,
3903                           player2Name, player2Rating);
3904
3905                 continue;
3906             }
3907
3908             /* Deal with automatic examine mode after a game,
3909                and with IcsObserving -> IcsExamining transition */
3910             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3911                 looking_at(buf, &i, "has made you an examiner of game *")) {
3912
3913                 int gamenum = atoi(star_match[0]);
3914                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3915                     gamenum == ics_gamenum) {
3916                     /* We were already playing or observing this game;
3917                        no need to refetch history */
3918                     gameMode = IcsExamining;
3919                     if (pausing) {
3920                         pauseExamForwardMostMove = forwardMostMove;
3921                     } else if (currentMove < forwardMostMove) {
3922                         ForwardInner(forwardMostMove);
3923                     }
3924                 } else {
3925                     /* I don't think this case really can happen */
3926                     SendToICS(ics_prefix);
3927                     SendToICS("refresh\n");
3928                 }
3929                 continue;
3930             }
3931
3932             /* Error messages */
3933 //          if (ics_user_moved) {
3934             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3935                 if (looking_at(buf, &i, "Illegal move") ||
3936                     looking_at(buf, &i, "Not a legal move") ||
3937                     looking_at(buf, &i, "Your king is in check") ||
3938                     looking_at(buf, &i, "It isn't your turn") ||
3939                     looking_at(buf, &i, "It is not your move")) {
3940                     /* Illegal move */
3941                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3942                         currentMove = forwardMostMove-1;
3943                         DisplayMove(currentMove - 1); /* before DMError */
3944                         DrawPosition(FALSE, boards[currentMove]);
3945                         SwitchClocks(forwardMostMove-1); // [HGM] race
3946                         DisplayBothClocks();
3947                     }
3948                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3949                     ics_user_moved = 0;
3950                     continue;
3951                 }
3952             }
3953
3954             if (looking_at(buf, &i, "still have time") ||
3955                 looking_at(buf, &i, "not out of time") ||
3956                 looking_at(buf, &i, "either player is out of time") ||
3957                 looking_at(buf, &i, "has timeseal; checking")) {
3958                 /* We must have called his flag a little too soon */
3959                 whiteFlag = blackFlag = FALSE;
3960                 continue;
3961             }
3962
3963             if (looking_at(buf, &i, "added * seconds to") ||
3964                 looking_at(buf, &i, "seconds were added to")) {
3965                 /* Update the clocks */
3966                 SendToICS(ics_prefix);
3967                 SendToICS("refresh\n");
3968                 continue;
3969             }
3970
3971             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3972                 ics_clock_paused = TRUE;
3973                 StopClocks();
3974                 continue;
3975             }
3976
3977             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3978                 ics_clock_paused = FALSE;
3979                 StartClocks();
3980                 continue;
3981             }
3982
3983             /* Grab player ratings from the Creating: message.
3984                Note we have to check for the special case when
3985                the ICS inserts things like [white] or [black]. */
3986             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3987                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3988                 /* star_matches:
3989                    0    player 1 name (not necessarily white)
3990                    1    player 1 rating
3991                    2    empty, white, or black (IGNORED)
3992                    3    player 2 name (not necessarily black)
3993                    4    player 2 rating
3994
3995                    The names/ratings are sorted out when the game
3996                    actually starts (below).
3997                 */
3998                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3999                 player1Rating = string_to_rating(star_match[1]);
4000                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4001                 player2Rating = string_to_rating(star_match[4]);
4002
4003                 if (appData.debugMode)
4004                   fprintf(debugFP,
4005                           "Ratings from 'Creating:' %s %d, %s %d\n",
4006                           player1Name, player1Rating,
4007                           player2Name, player2Rating);
4008
4009                 continue;
4010             }
4011
4012             /* Improved generic start/end-of-game messages */
4013             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4014                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4015                 /* If tkind == 0: */
4016                 /* star_match[0] is the game number */
4017                 /*           [1] is the white player's name */
4018                 /*           [2] is the black player's name */
4019                 /* For end-of-game: */
4020                 /*           [3] is the reason for the game end */
4021                 /*           [4] is a PGN end game-token, preceded by " " */
4022                 /* For start-of-game: */
4023                 /*           [3] begins with "Creating" or "Continuing" */
4024                 /*           [4] is " *" or empty (don't care). */
4025                 int gamenum = atoi(star_match[0]);
4026                 char *whitename, *blackname, *why, *endtoken;
4027                 ChessMove endtype = EndOfFile;
4028
4029                 if (tkind == 0) {
4030                   whitename = star_match[1];
4031                   blackname = star_match[2];
4032                   why = star_match[3];
4033                   endtoken = star_match[4];
4034                 } else {
4035                   whitename = star_match[1];
4036                   blackname = star_match[3];
4037                   why = star_match[5];
4038                   endtoken = star_match[6];
4039                 }
4040
4041                 /* Game start messages */
4042                 if (strncmp(why, "Creating ", 9) == 0 ||
4043                     strncmp(why, "Continuing ", 11) == 0) {
4044                     gs_gamenum = gamenum;
4045                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4046                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4047                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4048 #if ZIPPY
4049                     if (appData.zippyPlay) {
4050                         ZippyGameStart(whitename, blackname);
4051                     }
4052 #endif /*ZIPPY*/
4053                     partnerBoardValid = FALSE; // [HGM] bughouse
4054                     continue;
4055                 }
4056
4057                 /* Game end messages */
4058                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4059                     ics_gamenum != gamenum) {
4060                     continue;
4061                 }
4062                 while (endtoken[0] == ' ') endtoken++;
4063                 switch (endtoken[0]) {
4064                   case '*':
4065                   default:
4066                     endtype = GameUnfinished;
4067                     break;
4068                   case '0':
4069                     endtype = BlackWins;
4070                     break;
4071                   case '1':
4072                     if (endtoken[1] == '/')
4073                       endtype = GameIsDrawn;
4074                     else
4075                       endtype = WhiteWins;
4076                     break;
4077                 }
4078                 GameEnds(endtype, why, GE_ICS);
4079 #if ZIPPY
4080                 if (appData.zippyPlay && first.initDone) {
4081                     ZippyGameEnd(endtype, why);
4082                     if (first.pr == NoProc) {
4083                       /* Start the next process early so that we'll
4084                          be ready for the next challenge */
4085                       StartChessProgram(&first);
4086                     }
4087                     /* Send "new" early, in case this command takes
4088                        a long time to finish, so that we'll be ready
4089                        for the next challenge. */
4090                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4091                     Reset(TRUE, TRUE);
4092                 }
4093 #endif /*ZIPPY*/
4094                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4095                 continue;
4096             }
4097
4098             if (looking_at(buf, &i, "Removing game * from observation") ||
4099                 looking_at(buf, &i, "no longer observing game *") ||
4100                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4101                 if (gameMode == IcsObserving &&
4102                     atoi(star_match[0]) == ics_gamenum)
4103                   {
4104                       /* icsEngineAnalyze */
4105                       if (appData.icsEngineAnalyze) {
4106                             ExitAnalyzeMode();
4107                             ModeHighlight();
4108                       }
4109                       StopClocks();
4110                       gameMode = IcsIdle;
4111                       ics_gamenum = -1;
4112                       ics_user_moved = FALSE;
4113                   }
4114                 continue;
4115             }
4116
4117             if (looking_at(buf, &i, "no longer examining game *")) {
4118                 if (gameMode == IcsExamining &&
4119                     atoi(star_match[0]) == ics_gamenum)
4120                   {
4121                       gameMode = IcsIdle;
4122                       ics_gamenum = -1;
4123                       ics_user_moved = FALSE;
4124                   }
4125                 continue;
4126             }
4127
4128             /* Advance leftover_start past any newlines we find,
4129                so only partial lines can get reparsed */
4130             if (looking_at(buf, &i, "\n")) {
4131                 prevColor = curColor;
4132                 if (curColor != ColorNormal) {
4133                     if (oldi > next_out) {
4134                         SendToPlayer(&buf[next_out], oldi - next_out);
4135                         next_out = oldi;
4136                     }
4137                     Colorize(ColorNormal, FALSE);
4138                     curColor = ColorNormal;
4139                 }
4140                 if (started == STARTED_BOARD) {
4141                     started = STARTED_NONE;
4142                     parse[parse_pos] = NULLCHAR;
4143                     ParseBoard12(parse);
4144                     ics_user_moved = 0;
4145
4146                     /* Send premove here */
4147                     if (appData.premove) {
4148                       char str[MSG_SIZ];
4149                       if (currentMove == 0 &&
4150                           gameMode == IcsPlayingWhite &&
4151                           appData.premoveWhite) {
4152                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4153                         if (appData.debugMode)
4154                           fprintf(debugFP, "Sending premove:\n");
4155                         SendToICS(str);
4156                       } else if (currentMove == 1 &&
4157                                  gameMode == IcsPlayingBlack &&
4158                                  appData.premoveBlack) {
4159                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4160                         if (appData.debugMode)
4161                           fprintf(debugFP, "Sending premove:\n");
4162                         SendToICS(str);
4163                       } else if (gotPremove) {
4164                         gotPremove = 0;
4165                         ClearPremoveHighlights();
4166                         if (appData.debugMode)
4167                           fprintf(debugFP, "Sending premove:\n");
4168                           UserMoveEvent(premoveFromX, premoveFromY,
4169                                         premoveToX, premoveToY,
4170                                         premovePromoChar);
4171                       }
4172                     }
4173
4174                     /* Usually suppress following prompt */
4175                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4176                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4177                         if (looking_at(buf, &i, "*% ")) {
4178                             savingComment = FALSE;
4179                             suppressKibitz = 0;
4180                         }
4181                     }
4182                     next_out = i;
4183                 } else if (started == STARTED_HOLDINGS) {
4184                     int gamenum;
4185                     char new_piece[MSG_SIZ];
4186                     started = STARTED_NONE;
4187                     parse[parse_pos] = NULLCHAR;
4188                     if (appData.debugMode)
4189                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4190                                                         parse, currentMove);
4191                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4192                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4193                         if (gameInfo.variant == VariantNormal) {
4194                           /* [HGM] We seem to switch variant during a game!
4195                            * Presumably no holdings were displayed, so we have
4196                            * to move the position two files to the right to
4197                            * create room for them!
4198                            */
4199                           VariantClass newVariant;
4200                           switch(gameInfo.boardWidth) { // base guess on board width
4201                                 case 9:  newVariant = VariantShogi; break;
4202                                 case 10: newVariant = VariantGreat; break;
4203                                 default: newVariant = VariantCrazyhouse; break;
4204                           }
4205                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4206                           /* Get a move list just to see the header, which
4207                              will tell us whether this is really bug or zh */
4208                           if (ics_getting_history == H_FALSE) {
4209                             ics_getting_history = H_REQUESTED;
4210                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4211                             SendToICS(str);
4212                           }
4213                         }
4214                         new_piece[0] = NULLCHAR;
4215                         sscanf(parse, "game %d white [%s black [%s <- %s",
4216                                &gamenum, white_holding, black_holding,
4217                                new_piece);
4218                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4219                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4220                         /* [HGM] copy holdings to board holdings area */
4221                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4222                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4223                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4224 #if ZIPPY
4225                         if (appData.zippyPlay && first.initDone) {
4226                             ZippyHoldings(white_holding, black_holding,
4227                                           new_piece);
4228                         }
4229 #endif /*ZIPPY*/
4230                         if (tinyLayout || smallLayout) {
4231                             char wh[16], bh[16];
4232                             PackHolding(wh, white_holding);
4233                             PackHolding(bh, black_holding);
4234                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4235                                     gameInfo.white, gameInfo.black);
4236                         } else {
4237                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4238                                     gameInfo.white, white_holding, _("vs."),
4239                                     gameInfo.black, black_holding);
4240                         }
4241                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4242                         DrawPosition(FALSE, boards[currentMove]);
4243                         DisplayTitle(str);
4244                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4245                         sscanf(parse, "game %d white [%s black [%s <- %s",
4246                                &gamenum, white_holding, black_holding,
4247                                new_piece);
4248                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4249                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4250                         /* [HGM] copy holdings to partner-board holdings area */
4251                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4252                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4253                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4254                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4255                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4256                       }
4257                     }
4258                     /* Suppress following prompt */
4259                     if (looking_at(buf, &i, "*% ")) {
4260                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4261                         savingComment = FALSE;
4262                         suppressKibitz = 0;
4263                     }
4264                     next_out = i;
4265                 }
4266                 continue;
4267             }
4268
4269             i++;                /* skip unparsed character and loop back */
4270         }
4271
4272         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4273 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4274 //          SendToPlayer(&buf[next_out], i - next_out);
4275             started != STARTED_HOLDINGS && leftover_start > next_out) {
4276             SendToPlayer(&buf[next_out], leftover_start - next_out);
4277             next_out = i;
4278         }
4279
4280         leftover_len = buf_len - leftover_start;
4281         /* if buffer ends with something we couldn't parse,
4282            reparse it after appending the next read */
4283
4284     } else if (count == 0) {
4285         RemoveInputSource(isr);
4286         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4287     } else {
4288         DisplayFatalError(_("Error reading from ICS"), error, 1);
4289     }
4290 }
4291
4292
4293 /* Board style 12 looks like this:
4294
4295    <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
4296
4297  * The "<12> " is stripped before it gets to this routine.  The two
4298  * trailing 0's (flip state and clock ticking) are later addition, and
4299  * some chess servers may not have them, or may have only the first.
4300  * Additional trailing fields may be added in the future.
4301  */
4302
4303 #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"
4304
4305 #define RELATION_OBSERVING_PLAYED    0
4306 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4307 #define RELATION_PLAYING_MYMOVE      1
4308 #define RELATION_PLAYING_NOTMYMOVE  -1
4309 #define RELATION_EXAMINING           2
4310 #define RELATION_ISOLATED_BOARD     -3
4311 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4312
4313 void
4314 ParseBoard12 (char *string)
4315 {
4316 #if ZIPPY
4317     int i, takeback;
4318     char *bookHit = NULL; // [HGM] book
4319 #endif
4320     GameMode newGameMode;
4321     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4322     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4323     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4324     char to_play, board_chars[200];
4325     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4326     char black[32], white[32];
4327     Board board;
4328     int prevMove = currentMove;
4329     int ticking = 2;
4330     ChessMove moveType;
4331     int fromX, fromY, toX, toY;
4332     char promoChar;
4333     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4334     Boolean weird = FALSE, reqFlag = FALSE;
4335
4336     fromX = fromY = toX = toY = -1;
4337
4338     newGame = FALSE;
4339
4340     if (appData.debugMode)
4341       fprintf(debugFP, "Parsing board: %s\n", string);
4342
4343     move_str[0] = NULLCHAR;
4344     elapsed_time[0] = NULLCHAR;
4345     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4346         int  i = 0, j;
4347         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4348             if(string[i] == ' ') { ranks++; files = 0; }
4349             else files++;
4350             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4351             i++;
4352         }
4353         for(j = 0; j <i; j++) board_chars[j] = string[j];
4354         board_chars[i] = '\0';
4355         string += i + 1;
4356     }
4357     n = sscanf(string, PATTERN, &to_play, &double_push,
4358                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4359                &gamenum, white, black, &relation, &basetime, &increment,
4360                &white_stren, &black_stren, &white_time, &black_time,
4361                &moveNum, str, elapsed_time, move_str, &ics_flip,
4362                &ticking);
4363
4364     if (n < 21) {
4365         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4366         DisplayError(str, 0);
4367         return;
4368     }
4369
4370     /* Convert the move number to internal form */
4371     moveNum = (moveNum - 1) * 2;
4372     if (to_play == 'B') moveNum++;
4373     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4374       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4375                         0, 1);
4376       return;
4377     }
4378
4379     switch (relation) {
4380       case RELATION_OBSERVING_PLAYED:
4381       case RELATION_OBSERVING_STATIC:
4382         if (gamenum == -1) {
4383             /* Old ICC buglet */
4384             relation = RELATION_OBSERVING_STATIC;
4385         }
4386         newGameMode = IcsObserving;
4387         break;
4388       case RELATION_PLAYING_MYMOVE:
4389       case RELATION_PLAYING_NOTMYMOVE:
4390         newGameMode =
4391           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4392             IcsPlayingWhite : IcsPlayingBlack;
4393         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4394         break;
4395       case RELATION_EXAMINING:
4396         newGameMode = IcsExamining;
4397         break;
4398       case RELATION_ISOLATED_BOARD:
4399       default:
4400         /* Just display this board.  If user was doing something else,
4401            we will forget about it until the next board comes. */
4402         newGameMode = IcsIdle;
4403         break;
4404       case RELATION_STARTING_POSITION:
4405         newGameMode = gameMode;
4406         break;
4407     }
4408
4409     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4410         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4411          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4412       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4413       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4414       static int lastBgGame = -1;
4415       char *toSqr;
4416       for (k = 0; k < ranks; k++) {
4417         for (j = 0; j < files; j++)
4418           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4419         if(gameInfo.holdingsWidth > 1) {
4420              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4421              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4422         }
4423       }
4424       CopyBoard(partnerBoard, board);
4425       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4426         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4427         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4428       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4429       if(toSqr = strchr(str, '-')) {
4430         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4431         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4432       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4433       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4434       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4435       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4436       if(twoBoards) {
4437           DisplayWhiteClock(white_time*fac, to_play == 'W');
4438           DisplayBlackClock(black_time*fac, to_play != 'W');
4439           activePartner = to_play;
4440           if(gamenum != lastBgGame) {
4441               char buf[MSG_SIZ];
4442               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4443               DisplayTitle(buf);
4444           }
4445           lastBgGame = gamenum;
4446           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4447                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4448       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4449                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4450       if(!twoBoards) DisplayMessage(partnerStatus, "");
4451         partnerBoardValid = TRUE;
4452       return;
4453     }
4454
4455     if(appData.dualBoard && appData.bgObserve) {
4456         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4457             SendToICS(ics_prefix), SendToICS("pobserve\n");
4458         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4459             char buf[MSG_SIZ];
4460             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4461             SendToICS(buf);
4462         }
4463     }
4464
4465     /* Modify behavior for initial board display on move listing
4466        of wild games.
4467        */
4468     switch (ics_getting_history) {
4469       case H_FALSE:
4470       case H_REQUESTED:
4471         break;
4472       case H_GOT_REQ_HEADER:
4473       case H_GOT_UNREQ_HEADER:
4474         /* This is the initial position of the current game */
4475         gamenum = ics_gamenum;
4476         moveNum = 0;            /* old ICS bug workaround */
4477         if (to_play == 'B') {
4478           startedFromSetupPosition = TRUE;
4479           blackPlaysFirst = TRUE;
4480           moveNum = 1;
4481           if (forwardMostMove == 0) forwardMostMove = 1;
4482           if (backwardMostMove == 0) backwardMostMove = 1;
4483           if (currentMove == 0) currentMove = 1;
4484         }
4485         newGameMode = gameMode;
4486         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4487         break;
4488       case H_GOT_UNWANTED_HEADER:
4489         /* This is an initial board that we don't want */
4490         return;
4491       case H_GETTING_MOVES:
4492         /* Should not happen */
4493         DisplayError(_("Error gathering move list: extra board"), 0);
4494         ics_getting_history = H_FALSE;
4495         return;
4496     }
4497
4498    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4499                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4500                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4501      /* [HGM] We seem to have switched variant unexpectedly
4502       * Try to guess new variant from board size
4503       */
4504           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4505           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4506           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4507           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4508           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4509           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4510           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4511           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4512           /* Get a move list just to see the header, which
4513              will tell us whether this is really bug or zh */
4514           if (ics_getting_history == H_FALSE) {
4515             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4516             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4517             SendToICS(str);
4518           }
4519     }
4520
4521     /* Take action if this is the first board of a new game, or of a
4522        different game than is currently being displayed.  */
4523     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4524         relation == RELATION_ISOLATED_BOARD) {
4525
4526         /* Forget the old game and get the history (if any) of the new one */
4527         if (gameMode != BeginningOfGame) {
4528           Reset(TRUE, TRUE);
4529         }
4530         newGame = TRUE;
4531         if (appData.autoRaiseBoard) BoardToTop();
4532         prevMove = -3;
4533         if (gamenum == -1) {
4534             newGameMode = IcsIdle;
4535         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4536                    appData.getMoveList && !reqFlag) {
4537             /* Need to get game history */
4538             ics_getting_history = H_REQUESTED;
4539             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4540             SendToICS(str);
4541         }
4542
4543         /* Initially flip the board to have black on the bottom if playing
4544            black or if the ICS flip flag is set, but let the user change
4545            it with the Flip View button. */
4546         flipView = appData.autoFlipView ?
4547           (newGameMode == IcsPlayingBlack) || ics_flip :
4548           appData.flipView;
4549
4550         /* Done with values from previous mode; copy in new ones */
4551         gameMode = newGameMode;
4552         ModeHighlight();
4553         ics_gamenum = gamenum;
4554         if (gamenum == gs_gamenum) {
4555             int klen = strlen(gs_kind);
4556             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4557             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4558             gameInfo.event = StrSave(str);
4559         } else {
4560             gameInfo.event = StrSave("ICS game");
4561         }
4562         gameInfo.site = StrSave(appData.icsHost);
4563         gameInfo.date = PGNDate();
4564         gameInfo.round = StrSave("-");
4565         gameInfo.white = StrSave(white);
4566         gameInfo.black = StrSave(black);
4567         timeControl = basetime * 60 * 1000;
4568         timeControl_2 = 0;
4569         timeIncrement = increment * 1000;
4570         movesPerSession = 0;
4571         gameInfo.timeControl = TimeControlTagValue();
4572         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4573   if (appData.debugMode) {
4574     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4575     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4576     setbuf(debugFP, NULL);
4577   }
4578
4579         gameInfo.outOfBook = NULL;
4580
4581         /* Do we have the ratings? */
4582         if (strcmp(player1Name, white) == 0 &&
4583             strcmp(player2Name, black) == 0) {
4584             if (appData.debugMode)
4585               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4586                       player1Rating, player2Rating);
4587             gameInfo.whiteRating = player1Rating;
4588             gameInfo.blackRating = player2Rating;
4589         } else if (strcmp(player2Name, white) == 0 &&
4590                    strcmp(player1Name, black) == 0) {
4591             if (appData.debugMode)
4592               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4593                       player2Rating, player1Rating);
4594             gameInfo.whiteRating = player2Rating;
4595             gameInfo.blackRating = player1Rating;
4596         }
4597         player1Name[0] = player2Name[0] = NULLCHAR;
4598
4599         /* Silence shouts if requested */
4600         if (appData.quietPlay &&
4601             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4602             SendToICS(ics_prefix);
4603             SendToICS("set shout 0\n");
4604         }
4605     }
4606
4607     /* Deal with midgame name changes */
4608     if (!newGame) {
4609         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4610             if (gameInfo.white) free(gameInfo.white);
4611             gameInfo.white = StrSave(white);
4612         }
4613         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4614             if (gameInfo.black) free(gameInfo.black);
4615             gameInfo.black = StrSave(black);
4616         }
4617     }
4618
4619     /* Throw away game result if anything actually changes in examine mode */
4620     if (gameMode == IcsExamining && !newGame) {
4621         gameInfo.result = GameUnfinished;
4622         if (gameInfo.resultDetails != NULL) {
4623             free(gameInfo.resultDetails);
4624             gameInfo.resultDetails = NULL;
4625         }
4626     }
4627
4628     /* In pausing && IcsExamining mode, we ignore boards coming
4629        in if they are in a different variation than we are. */
4630     if (pauseExamInvalid) return;
4631     if (pausing && gameMode == IcsExamining) {
4632         if (moveNum <= pauseExamForwardMostMove) {
4633             pauseExamInvalid = TRUE;
4634             forwardMostMove = pauseExamForwardMostMove;
4635             return;
4636         }
4637     }
4638
4639   if (appData.debugMode) {
4640     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4641   }
4642     /* Parse the board */
4643     for (k = 0; k < ranks; k++) {
4644       for (j = 0; j < files; j++)
4645         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4646       if(gameInfo.holdingsWidth > 1) {
4647            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4648            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4649       }
4650     }
4651     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4652       board[5][BOARD_RGHT+1] = WhiteAngel;
4653       board[6][BOARD_RGHT+1] = WhiteMarshall;
4654       board[1][0] = BlackMarshall;
4655       board[2][0] = BlackAngel;
4656       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4657     }
4658     CopyBoard(boards[moveNum], board);
4659     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4660     if (moveNum == 0) {
4661         startedFromSetupPosition =
4662           !CompareBoards(board, initialPosition);
4663         if(startedFromSetupPosition)
4664             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4665     }
4666
4667     /* [HGM] Set castling rights. Take the outermost Rooks,
4668        to make it also work for FRC opening positions. Note that board12
4669        is really defective for later FRC positions, as it has no way to
4670        indicate which Rook can castle if they are on the same side of King.
4671        For the initial position we grant rights to the outermost Rooks,
4672        and remember thos rights, and we then copy them on positions
4673        later in an FRC game. This means WB might not recognize castlings with
4674        Rooks that have moved back to their original position as illegal,
4675        but in ICS mode that is not its job anyway.
4676     */
4677     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4678     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4679
4680         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4681             if(board[0][i] == WhiteRook) j = i;
4682         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4683         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4684             if(board[0][i] == WhiteRook) j = i;
4685         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4686         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4687             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4688         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4689         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4690             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4691         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4692
4693         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4694         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4695         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4696             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4697         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4698             if(board[BOARD_HEIGHT-1][k] == bKing)
4699                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4700         if(gameInfo.variant == VariantTwoKings) {
4701             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4702             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4703             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4704         }
4705     } else { int r;
4706         r = boards[moveNum][CASTLING][0] = initialRights[0];
4707         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4708         r = boards[moveNum][CASTLING][1] = initialRights[1];
4709         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4710         r = boards[moveNum][CASTLING][3] = initialRights[3];
4711         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4712         r = boards[moveNum][CASTLING][4] = initialRights[4];
4713         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4714         /* wildcastle kludge: always assume King has rights */
4715         r = boards[moveNum][CASTLING][2] = initialRights[2];
4716         r = boards[moveNum][CASTLING][5] = initialRights[5];
4717     }
4718     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4719     boards[moveNum][EP_STATUS] = EP_NONE;
4720     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4721     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4722     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4723
4724
4725     if (ics_getting_history == H_GOT_REQ_HEADER ||
4726         ics_getting_history == H_GOT_UNREQ_HEADER) {
4727         /* This was an initial position from a move list, not
4728            the current position */
4729         return;
4730     }
4731
4732     /* Update currentMove and known move number limits */
4733     newMove = newGame || moveNum > forwardMostMove;
4734
4735     if (newGame) {
4736         forwardMostMove = backwardMostMove = currentMove = moveNum;
4737         if (gameMode == IcsExamining && moveNum == 0) {
4738           /* Workaround for ICS limitation: we are not told the wild
4739              type when starting to examine a game.  But if we ask for
4740              the move list, the move list header will tell us */
4741             ics_getting_history = H_REQUESTED;
4742             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4743             SendToICS(str);
4744         }
4745     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4746                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4747 #if ZIPPY
4748         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4749         /* [HGM] applied this also to an engine that is silently watching        */
4750         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4751             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4752             gameInfo.variant == currentlyInitializedVariant) {
4753           takeback = forwardMostMove - moveNum;
4754           for (i = 0; i < takeback; i++) {
4755             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4756             SendToProgram("undo\n", &first);
4757           }
4758         }
4759 #endif
4760
4761         forwardMostMove = moveNum;
4762         if (!pausing || currentMove > forwardMostMove)
4763           currentMove = forwardMostMove;
4764     } else {
4765         /* New part of history that is not contiguous with old part */
4766         if (pausing && gameMode == IcsExamining) {
4767             pauseExamInvalid = TRUE;
4768             forwardMostMove = pauseExamForwardMostMove;
4769             return;
4770         }
4771         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4772 #if ZIPPY
4773             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4774                 // [HGM] when we will receive the move list we now request, it will be
4775                 // fed to the engine from the first move on. So if the engine is not
4776                 // in the initial position now, bring it there.
4777                 InitChessProgram(&first, 0);
4778             }
4779 #endif
4780             ics_getting_history = H_REQUESTED;
4781             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4782             SendToICS(str);
4783         }
4784         forwardMostMove = backwardMostMove = currentMove = moveNum;
4785     }
4786
4787     /* Update the clocks */
4788     if (strchr(elapsed_time, '.')) {
4789       /* Time is in ms */
4790       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4791       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4792     } else {
4793       /* Time is in seconds */
4794       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4795       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4796     }
4797
4798
4799 #if ZIPPY
4800     if (appData.zippyPlay && newGame &&
4801         gameMode != IcsObserving && gameMode != IcsIdle &&
4802         gameMode != IcsExamining)
4803       ZippyFirstBoard(moveNum, basetime, increment);
4804 #endif
4805
4806     /* Put the move on the move list, first converting
4807        to canonical algebraic form. */
4808     if (moveNum > 0) {
4809   if (appData.debugMode) {
4810     int f = forwardMostMove;
4811     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4812             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4813             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4814     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4815     fprintf(debugFP, "moveNum = %d\n", moveNum);
4816     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4817     setbuf(debugFP, NULL);
4818   }
4819         if (moveNum <= backwardMostMove) {
4820             /* We don't know what the board looked like before
4821                this move.  Punt. */
4822           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4823             strcat(parseList[moveNum - 1], " ");
4824             strcat(parseList[moveNum - 1], elapsed_time);
4825             moveList[moveNum - 1][0] = NULLCHAR;
4826         } else if (strcmp(move_str, "none") == 0) {
4827             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4828             /* Again, we don't know what the board looked like;
4829                this is really the start of the game. */
4830             parseList[moveNum - 1][0] = NULLCHAR;
4831             moveList[moveNum - 1][0] = NULLCHAR;
4832             backwardMostMove = moveNum;
4833             startedFromSetupPosition = TRUE;
4834             fromX = fromY = toX = toY = -1;
4835         } else {
4836           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4837           //                 So we parse the long-algebraic move string in stead of the SAN move
4838           int valid; char buf[MSG_SIZ], *prom;
4839
4840           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4841                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4842           // str looks something like "Q/a1-a2"; kill the slash
4843           if(str[1] == '/')
4844             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4845           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4846           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4847                 strcat(buf, prom); // long move lacks promo specification!
4848           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4849                 if(appData.debugMode)
4850                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4851                 safeStrCpy(move_str, buf, MSG_SIZ);
4852           }
4853           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4854                                 &fromX, &fromY, &toX, &toY, &promoChar)
4855                || ParseOneMove(buf, moveNum - 1, &moveType,
4856                                 &fromX, &fromY, &toX, &toY, &promoChar);
4857           // end of long SAN patch
4858           if (valid) {
4859             (void) CoordsToAlgebraic(boards[moveNum - 1],
4860                                      PosFlags(moveNum - 1),
4861                                      fromY, fromX, toY, toX, promoChar,
4862                                      parseList[moveNum-1]);
4863             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4864               case MT_NONE:
4865               case MT_STALEMATE:
4866               default:
4867                 break;
4868               case MT_CHECK:
4869                 if(!IS_SHOGI(gameInfo.variant))
4870                     strcat(parseList[moveNum - 1], "+");
4871                 break;
4872               case MT_CHECKMATE:
4873               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4874                 strcat(parseList[moveNum - 1], "#");
4875                 break;
4876             }
4877             strcat(parseList[moveNum - 1], " ");
4878             strcat(parseList[moveNum - 1], elapsed_time);
4879             /* currentMoveString is set as a side-effect of ParseOneMove */
4880             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4881             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4882             strcat(moveList[moveNum - 1], "\n");
4883
4884             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4885                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4886               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4887                 ChessSquare old, new = boards[moveNum][k][j];
4888                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4889                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4890                   if(old == new) continue;
4891                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4892                   else if(new == WhiteWazir || new == BlackWazir) {
4893                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4894                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4895                       else boards[moveNum][k][j] = old; // preserve type of Gold
4896                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4897                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4898               }
4899           } else {
4900             /* Move from ICS was illegal!?  Punt. */
4901             if (appData.debugMode) {
4902               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4903               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4904             }
4905             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4906             strcat(parseList[moveNum - 1], " ");
4907             strcat(parseList[moveNum - 1], elapsed_time);
4908             moveList[moveNum - 1][0] = NULLCHAR;
4909             fromX = fromY = toX = toY = -1;
4910           }
4911         }
4912   if (appData.debugMode) {
4913     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4914     setbuf(debugFP, NULL);
4915   }
4916
4917 #if ZIPPY
4918         /* Send move to chess program (BEFORE animating it). */
4919         if (appData.zippyPlay && !newGame && newMove &&
4920            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4921
4922             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4923                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4924                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4925                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4926                             move_str);
4927                     DisplayError(str, 0);
4928                 } else {
4929                     if (first.sendTime) {
4930                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4931                     }
4932                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4933                     if (firstMove && !bookHit) {
4934                         firstMove = FALSE;
4935                         if (first.useColors) {
4936                           SendToProgram(gameMode == IcsPlayingWhite ?
4937                                         "white\ngo\n" :
4938                                         "black\ngo\n", &first);
4939                         } else {
4940                           SendToProgram("go\n", &first);
4941                         }
4942                         first.maybeThinking = TRUE;
4943                     }
4944                 }
4945             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4946               if (moveList[moveNum - 1][0] == NULLCHAR) {
4947                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4948                 DisplayError(str, 0);
4949               } else {
4950                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4951                 SendMoveToProgram(moveNum - 1, &first);
4952               }
4953             }
4954         }
4955 #endif
4956     }
4957
4958     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4959         /* If move comes from a remote source, animate it.  If it
4960            isn't remote, it will have already been animated. */
4961         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4962             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4963         }
4964         if (!pausing && appData.highlightLastMove) {
4965             SetHighlights(fromX, fromY, toX, toY);
4966         }
4967     }
4968
4969     /* Start the clocks */
4970     whiteFlag = blackFlag = FALSE;
4971     appData.clockMode = !(basetime == 0 && increment == 0);
4972     if (ticking == 0) {
4973       ics_clock_paused = TRUE;
4974       StopClocks();
4975     } else if (ticking == 1) {
4976       ics_clock_paused = FALSE;
4977     }
4978     if (gameMode == IcsIdle ||
4979         relation == RELATION_OBSERVING_STATIC ||
4980         relation == RELATION_EXAMINING ||
4981         ics_clock_paused)
4982       DisplayBothClocks();
4983     else
4984       StartClocks();
4985
4986     /* Display opponents and material strengths */
4987     if (gameInfo.variant != VariantBughouse &&
4988         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4989         if (tinyLayout || smallLayout) {
4990             if(gameInfo.variant == VariantNormal)
4991               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4992                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4993                     basetime, increment);
4994             else
4995               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4996                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4997                     basetime, increment, (int) gameInfo.variant);
4998         } else {
4999             if(gameInfo.variant == VariantNormal)
5000               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5001                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5002                     basetime, increment);
5003             else
5004               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5005                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5006                     basetime, increment, VariantName(gameInfo.variant));
5007         }
5008         DisplayTitle(str);
5009   if (appData.debugMode) {
5010     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5011   }
5012     }
5013
5014
5015     /* Display the board */
5016     if (!pausing && !appData.noGUI) {
5017
5018       if (appData.premove)
5019           if (!gotPremove ||
5020              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5021              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5022               ClearPremoveHighlights();
5023
5024       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5025         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5026       DrawPosition(j, boards[currentMove]);
5027
5028       DisplayMove(moveNum - 1);
5029       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5030             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5031               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5032         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5033       }
5034     }
5035
5036     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5037 #if ZIPPY
5038     if(bookHit) { // [HGM] book: simulate book reply
5039         static char bookMove[MSG_SIZ]; // a bit generous?
5040
5041         programStats.nodes = programStats.depth = programStats.time =
5042         programStats.score = programStats.got_only_move = 0;
5043         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5044
5045         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5046         strcat(bookMove, bookHit);
5047         HandleMachineMove(bookMove, &first);
5048     }
5049 #endif
5050 }
5051
5052 void
5053 GetMoveListEvent ()
5054 {
5055     char buf[MSG_SIZ];
5056     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5057         ics_getting_history = H_REQUESTED;
5058         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5059         SendToICS(buf);
5060     }
5061 }
5062
5063 void
5064 SendToBoth (char *msg)
5065 {   // to make it easy to keep two engines in step in dual analysis
5066     SendToProgram(msg, &first);
5067     if(second.analyzing) SendToProgram(msg, &second);
5068 }
5069
5070 void
5071 AnalysisPeriodicEvent (int force)
5072 {
5073     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5074          && !force) || !appData.periodicUpdates)
5075       return;
5076
5077     /* Send . command to Crafty to collect stats */
5078     SendToBoth(".\n");
5079
5080     /* Don't send another until we get a response (this makes
5081        us stop sending to old Crafty's which don't understand
5082        the "." command (sending illegal cmds resets node count & time,
5083        which looks bad)) */
5084     programStats.ok_to_send = 0;
5085 }
5086
5087 void
5088 ics_update_width (int new_width)
5089 {
5090         ics_printf("set width %d\n", new_width);
5091 }
5092
5093 void
5094 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5095 {
5096     char buf[MSG_SIZ];
5097
5098     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5099         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5100             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5101             SendToProgram(buf, cps);
5102             return;
5103         }
5104         // null move in variant where engine does not understand it (for analysis purposes)
5105         SendBoard(cps, moveNum + 1); // send position after move in stead.
5106         return;
5107     }
5108     if (cps->useUsermove) {
5109       SendToProgram("usermove ", cps);
5110     }
5111     if (cps->useSAN) {
5112       char *space;
5113       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5114         int len = space - parseList[moveNum];
5115         memcpy(buf, parseList[moveNum], len);
5116         buf[len++] = '\n';
5117         buf[len] = NULLCHAR;
5118       } else {
5119         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5120       }
5121       SendToProgram(buf, cps);
5122     } else {
5123       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5124         AlphaRank(moveList[moveNum], 4);
5125         SendToProgram(moveList[moveNum], cps);
5126         AlphaRank(moveList[moveNum], 4); // and back
5127       } else
5128       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5129        * the engine. It would be nice to have a better way to identify castle
5130        * moves here. */
5131       if(appData.fischerCastling && cps->useOOCastle) {
5132         int fromX = moveList[moveNum][0] - AAA;
5133         int fromY = moveList[moveNum][1] - ONE;
5134         int toX = moveList[moveNum][2] - AAA;
5135         int toY = moveList[moveNum][3] - ONE;
5136         if((boards[moveNum][fromY][fromX] == WhiteKing
5137             && boards[moveNum][toY][toX] == WhiteRook)
5138            || (boards[moveNum][fromY][fromX] == BlackKing
5139                && boards[moveNum][toY][toX] == BlackRook)) {
5140           if(toX > fromX) SendToProgram("O-O\n", cps);
5141           else SendToProgram("O-O-O\n", cps);
5142         }
5143         else SendToProgram(moveList[moveNum], cps);
5144       } else
5145       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5146         char *m = moveList[moveNum];
5147         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
5148           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5149                                                m[2], m[3] - '0',
5150                                                m[5], m[6] - '0',
5151                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5152         else
5153           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5154                                                m[5], m[6] - '0',
5155                                                m[5], m[6] - '0',
5156                                                m[2], m[3] - '0');
5157           SendToProgram(buf, cps);
5158       } else
5159       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5160         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5161           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5162           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5163                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5164         } else
5165           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5166                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5167         SendToProgram(buf, cps);
5168       }
5169       else SendToProgram(moveList[moveNum], cps);
5170       /* End of additions by Tord */
5171     }
5172
5173     /* [HGM] setting up the opening has brought engine in force mode! */
5174     /*       Send 'go' if we are in a mode where machine should play. */
5175     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5176         (gameMode == TwoMachinesPlay   ||
5177 #if ZIPPY
5178          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5179 #endif
5180          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5181         SendToProgram("go\n", cps);
5182   if (appData.debugMode) {
5183     fprintf(debugFP, "(extra)\n");
5184   }
5185     }
5186     setboardSpoiledMachineBlack = 0;
5187 }
5188
5189 void
5190 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5191 {
5192     char user_move[MSG_SIZ];
5193     char suffix[4];
5194
5195     if(gameInfo.variant == VariantSChess && promoChar) {
5196         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5197         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5198     } else suffix[0] = NULLCHAR;
5199
5200     switch (moveType) {
5201       default:
5202         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5203                 (int)moveType, fromX, fromY, toX, toY);
5204         DisplayError(user_move + strlen("say "), 0);
5205         break;
5206       case WhiteKingSideCastle:
5207       case BlackKingSideCastle:
5208       case WhiteQueenSideCastleWild:
5209       case BlackQueenSideCastleWild:
5210       /* PUSH Fabien */
5211       case WhiteHSideCastleFR:
5212       case BlackHSideCastleFR:
5213       /* POP Fabien */
5214         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5215         break;
5216       case WhiteQueenSideCastle:
5217       case BlackQueenSideCastle:
5218       case WhiteKingSideCastleWild:
5219       case BlackKingSideCastleWild:
5220       /* PUSH Fabien */
5221       case WhiteASideCastleFR:
5222       case BlackASideCastleFR:
5223       /* POP Fabien */
5224         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5225         break;
5226       case WhiteNonPromotion:
5227       case BlackNonPromotion:
5228         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5229         break;
5230       case WhitePromotion:
5231       case BlackPromotion:
5232         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5233            gameInfo.variant == VariantMakruk)
5234           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5235                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5236                 PieceToChar(WhiteFerz));
5237         else if(gameInfo.variant == VariantGreat)
5238           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5239                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5240                 PieceToChar(WhiteMan));
5241         else
5242           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5243                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5244                 promoChar);
5245         break;
5246       case WhiteDrop:
5247       case BlackDrop:
5248       drop:
5249         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5250                  ToUpper(PieceToChar((ChessSquare) fromX)),
5251                  AAA + toX, ONE + toY);
5252         break;
5253       case IllegalMove:  /* could be a variant we don't quite understand */
5254         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5255       case NormalMove:
5256       case WhiteCapturesEnPassant:
5257       case BlackCapturesEnPassant:
5258         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5259                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5260         break;
5261     }
5262     SendToICS(user_move);
5263     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5264         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5265 }
5266
5267 void
5268 UploadGameEvent ()
5269 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5270     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5271     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5272     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5273       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5274       return;
5275     }
5276     if(gameMode != IcsExamining) { // is this ever not the case?
5277         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5278
5279         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5280           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5281         } else { // on FICS we must first go to general examine mode
5282           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5283         }
5284         if(gameInfo.variant != VariantNormal) {
5285             // try figure out wild number, as xboard names are not always valid on ICS
5286             for(i=1; i<=36; i++) {
5287               snprintf(buf, MSG_SIZ, "wild/%d", i);
5288                 if(StringToVariant(buf) == gameInfo.variant) break;
5289             }
5290             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5291             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5292             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5293         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5294         SendToICS(ics_prefix);
5295         SendToICS(buf);
5296         if(startedFromSetupPosition || backwardMostMove != 0) {
5297           fen = PositionToFEN(backwardMostMove, NULL, 1);
5298           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5299             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5300             SendToICS(buf);
5301           } else { // FICS: everything has to set by separate bsetup commands
5302             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5303             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5304             SendToICS(buf);
5305             if(!WhiteOnMove(backwardMostMove)) {
5306                 SendToICS("bsetup tomove black\n");
5307             }
5308             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5309             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5310             SendToICS(buf);
5311             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5312             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5313             SendToICS(buf);
5314             i = boards[backwardMostMove][EP_STATUS];
5315             if(i >= 0) { // set e.p.
5316               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5317                 SendToICS(buf);
5318             }
5319             bsetup++;
5320           }
5321         }
5322       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5323             SendToICS("bsetup done\n"); // switch to normal examining.
5324     }
5325     for(i = backwardMostMove; i<last; i++) {
5326         char buf[20];
5327         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5328         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5329             int len = strlen(moveList[i]);
5330             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5331             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5332         }
5333         SendToICS(buf);
5334     }
5335     SendToICS(ics_prefix);
5336     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5337 }
5338
5339 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5340 int legNr = 1;
5341
5342 void
5343 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5344 {
5345     if (rf == DROP_RANK) {
5346       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5347       sprintf(move, "%c@%c%c\n",
5348                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5349     } else {
5350         if (promoChar == 'x' || promoChar == NULLCHAR) {
5351           sprintf(move, "%c%c%c%c\n",
5352                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5353           if(killX >= 0 && killY >= 0) {
5354             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5355             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + killX, ONE + killY);
5356           }
5357         } else {
5358             sprintf(move, "%c%c%c%c%c\n",
5359                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5360         }
5361     }
5362 }
5363
5364 void
5365 ProcessICSInitScript (FILE *f)
5366 {
5367     char buf[MSG_SIZ];
5368
5369     while (fgets(buf, MSG_SIZ, f)) {
5370         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5371     }
5372
5373     fclose(f);
5374 }
5375
5376
5377 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5378 int dragging;
5379 static ClickType lastClickType;
5380
5381 int
5382 Partner (ChessSquare *p)
5383 { // change piece into promotion partner if one shogi-promotes to the other
5384   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5385   ChessSquare partner;
5386   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5387   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5388   *p = partner;
5389   return 1;
5390 }
5391
5392 void
5393 Sweep (int step)
5394 {
5395     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5396     static int toggleFlag;
5397     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5398     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5399     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5400     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5401     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5402     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5403     do {
5404         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5405         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5406         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5407         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5408         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5409         if(!step) step = -1;
5410     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5411             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5412             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5413             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5414     if(toX >= 0) {
5415         int victim = boards[currentMove][toY][toX];
5416         boards[currentMove][toY][toX] = promoSweep;
5417         DrawPosition(FALSE, boards[currentMove]);
5418         boards[currentMove][toY][toX] = victim;
5419     } else
5420     ChangeDragPiece(promoSweep);
5421 }
5422
5423 int
5424 PromoScroll (int x, int y)
5425 {
5426   int step = 0;
5427
5428   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5429   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5430   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5431   if(!step) return FALSE;
5432   lastX = x; lastY = y;
5433   if((promoSweep < BlackPawn) == flipView) step = -step;
5434   if(step > 0) selectFlag = 1;
5435   if(!selectFlag) Sweep(step);
5436   return FALSE;
5437 }
5438
5439 void
5440 NextPiece (int step)
5441 {
5442     ChessSquare piece = boards[currentMove][toY][toX];
5443     do {
5444         pieceSweep -= step;
5445         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5446         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5447         if(!step) step = -1;
5448     } while(PieceToChar(pieceSweep) == '.');
5449     boards[currentMove][toY][toX] = pieceSweep;
5450     DrawPosition(FALSE, boards[currentMove]);
5451     boards[currentMove][toY][toX] = piece;
5452 }
5453 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5454 void
5455 AlphaRank (char *move, int n)
5456 {
5457 //    char *p = move, c; int x, y;
5458
5459     if (appData.debugMode) {
5460         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5461     }
5462
5463     if(move[1]=='*' &&
5464        move[2]>='0' && move[2]<='9' &&
5465        move[3]>='a' && move[3]<='x'    ) {
5466         move[1] = '@';
5467         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5468         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5469     } else
5470     if(move[0]>='0' && move[0]<='9' &&
5471        move[1]>='a' && move[1]<='x' &&
5472        move[2]>='0' && move[2]<='9' &&
5473        move[3]>='a' && move[3]<='x'    ) {
5474         /* input move, Shogi -> normal */
5475         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5476         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5477         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5478         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5479     } else
5480     if(move[1]=='@' &&
5481        move[3]>='0' && move[3]<='9' &&
5482        move[2]>='a' && move[2]<='x'    ) {
5483         move[1] = '*';
5484         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5485         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5486     } else
5487     if(
5488        move[0]>='a' && move[0]<='x' &&
5489        move[3]>='0' && move[3]<='9' &&
5490        move[2]>='a' && move[2]<='x'    ) {
5491          /* output move, normal -> Shogi */
5492         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5493         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5494         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5495         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5496         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5497     }
5498     if (appData.debugMode) {
5499         fprintf(debugFP, "   out = '%s'\n", move);
5500     }
5501 }
5502
5503 char yy_textstr[8000];
5504
5505 /* Parser for moves from gnuchess, ICS, or user typein box */
5506 Boolean
5507 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5508 {
5509     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5510
5511     switch (*moveType) {
5512       case WhitePromotion:
5513       case BlackPromotion:
5514       case WhiteNonPromotion:
5515       case BlackNonPromotion:
5516       case NormalMove:
5517       case FirstLeg:
5518       case WhiteCapturesEnPassant:
5519       case BlackCapturesEnPassant:
5520       case WhiteKingSideCastle:
5521       case WhiteQueenSideCastle:
5522       case BlackKingSideCastle:
5523       case BlackQueenSideCastle:
5524       case WhiteKingSideCastleWild:
5525       case WhiteQueenSideCastleWild:
5526       case BlackKingSideCastleWild:
5527       case BlackQueenSideCastleWild:
5528       /* Code added by Tord: */
5529       case WhiteHSideCastleFR:
5530       case WhiteASideCastleFR:
5531       case BlackHSideCastleFR:
5532       case BlackASideCastleFR:
5533       /* End of code added by Tord */
5534       case IllegalMove:         /* bug or odd chess variant */
5535         if(currentMoveString[1] == '@') { // illegal drop
5536           *fromX = WhiteOnMove(moveNum) ?
5537             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5538             (int) CharToPiece(ToLower(currentMoveString[0]));
5539           goto drop;
5540         }
5541         *fromX = currentMoveString[0] - AAA;
5542         *fromY = currentMoveString[1] - ONE;
5543         *toX = currentMoveString[2] - AAA;
5544         *toY = currentMoveString[3] - ONE;
5545         *promoChar = currentMoveString[4];
5546         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5547             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5548     if (appData.debugMode) {
5549         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5550     }
5551             *fromX = *fromY = *toX = *toY = 0;
5552             return FALSE;
5553         }
5554         if (appData.testLegality) {
5555           return (*moveType != IllegalMove);
5556         } else {
5557           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5558                          // [HGM] lion: if this is a double move we are less critical
5559                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5560         }
5561
5562       case WhiteDrop:
5563       case BlackDrop:
5564         *fromX = *moveType == WhiteDrop ?
5565           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5566           (int) CharToPiece(ToLower(currentMoveString[0]));
5567       drop:
5568         *fromY = DROP_RANK;
5569         *toX = currentMoveString[2] - AAA;
5570         *toY = currentMoveString[3] - ONE;
5571         *promoChar = NULLCHAR;
5572         return TRUE;
5573
5574       case AmbiguousMove:
5575       case ImpossibleMove:
5576       case EndOfFile:
5577       case ElapsedTime:
5578       case Comment:
5579       case PGNTag:
5580       case NAG:
5581       case WhiteWins:
5582       case BlackWins:
5583       case GameIsDrawn:
5584       default:
5585     if (appData.debugMode) {
5586         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5587     }
5588         /* bug? */
5589         *fromX = *fromY = *toX = *toY = 0;
5590         *promoChar = NULLCHAR;
5591         return FALSE;
5592     }
5593 }
5594
5595 Boolean pushed = FALSE;
5596 char *lastParseAttempt;
5597
5598 void
5599 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5600 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5601   int fromX, fromY, toX, toY; char promoChar;
5602   ChessMove moveType;
5603   Boolean valid;
5604   int nr = 0;
5605
5606   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5607   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5608     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5609     pushed = TRUE;
5610   }
5611   endPV = forwardMostMove;
5612   do {
5613     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5614     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5615     lastParseAttempt = pv;
5616     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5617     if(!valid && nr == 0 &&
5618        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5619         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5620         // Hande case where played move is different from leading PV move
5621         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5622         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5623         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5624         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5625           endPV += 2; // if position different, keep this
5626           moveList[endPV-1][0] = fromX + AAA;
5627           moveList[endPV-1][1] = fromY + ONE;
5628           moveList[endPV-1][2] = toX + AAA;
5629           moveList[endPV-1][3] = toY + ONE;
5630           parseList[endPV-1][0] = NULLCHAR;
5631           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5632         }
5633       }
5634     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5635     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5636     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5637     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5638         valid++; // allow comments in PV
5639         continue;
5640     }
5641     nr++;
5642     if(endPV+1 > framePtr) break; // no space, truncate
5643     if(!valid) break;
5644     endPV++;
5645     CopyBoard(boards[endPV], boards[endPV-1]);
5646     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5647     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5648     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5649     CoordsToAlgebraic(boards[endPV - 1],
5650                              PosFlags(endPV - 1),
5651                              fromY, fromX, toY, toX, promoChar,
5652                              parseList[endPV - 1]);
5653   } while(valid);
5654   if(atEnd == 2) return; // used hidden, for PV conversion
5655   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5656   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5657   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5658                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5659   DrawPosition(TRUE, boards[currentMove]);
5660 }
5661
5662 int
5663 MultiPV (ChessProgramState *cps)
5664 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5665         int i;
5666         for(i=0; i<cps->nrOptions; i++)
5667             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5668                 return i;
5669         return -1;
5670 }
5671
5672 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5673
5674 Boolean
5675 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5676 {
5677         int startPV, multi, lineStart, origIndex = index;
5678         char *p, buf2[MSG_SIZ];
5679         ChessProgramState *cps = (pane ? &second : &first);
5680
5681         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5682         lastX = x; lastY = y;
5683         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5684         lineStart = startPV = index;
5685         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5686         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5687         index = startPV;
5688         do{ while(buf[index] && buf[index] != '\n') index++;
5689         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5690         buf[index] = 0;
5691         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5692                 int n = cps->option[multi].value;
5693                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5694                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5695                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5696                 cps->option[multi].value = n;
5697                 *start = *end = 0;
5698                 return FALSE;
5699         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5700                 ExcludeClick(origIndex - lineStart);
5701                 return FALSE;
5702         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5703                 Collapse(origIndex - lineStart);
5704                 return FALSE;
5705         }
5706         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5707         *start = startPV; *end = index-1;
5708         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5709         return TRUE;
5710 }
5711
5712 char *
5713 PvToSAN (char *pv)
5714 {
5715         static char buf[10*MSG_SIZ];
5716         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5717         *buf = NULLCHAR;
5718         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5719         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5720         for(i = forwardMostMove; i<endPV; i++){
5721             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5722             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5723             k += strlen(buf+k);
5724         }
5725         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5726         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5727         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5728         endPV = savedEnd;
5729         return buf;
5730 }
5731
5732 Boolean
5733 LoadPV (int x, int y)
5734 { // called on right mouse click to load PV
5735   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5736   lastX = x; lastY = y;
5737   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5738   extendGame = FALSE;
5739   return TRUE;
5740 }
5741
5742 void
5743 UnLoadPV ()
5744 {
5745   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5746   if(endPV < 0) return;
5747   if(appData.autoCopyPV) CopyFENToClipboard();
5748   endPV = -1;
5749   if(extendGame && currentMove > forwardMostMove) {
5750         Boolean saveAnimate = appData.animate;
5751         if(pushed) {
5752             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5753                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5754             } else storedGames--; // abandon shelved tail of original game
5755         }
5756         pushed = FALSE;
5757         forwardMostMove = currentMove;
5758         currentMove = oldFMM;
5759         appData.animate = FALSE;
5760         ToNrEvent(forwardMostMove);
5761         appData.animate = saveAnimate;
5762   }
5763   currentMove = forwardMostMove;
5764   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5765   ClearPremoveHighlights();
5766   DrawPosition(TRUE, boards[currentMove]);
5767 }
5768
5769 void
5770 MovePV (int x, int y, int h)
5771 { // step through PV based on mouse coordinates (called on mouse move)
5772   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5773
5774   // we must somehow check if right button is still down (might be released off board!)
5775   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5776   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5777   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5778   if(!step) return;
5779   lastX = x; lastY = y;
5780
5781   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5782   if(endPV < 0) return;
5783   if(y < margin) step = 1; else
5784   if(y > h - margin) step = -1;
5785   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5786   currentMove += step;
5787   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5788   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5789                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5790   DrawPosition(FALSE, boards[currentMove]);
5791 }
5792
5793
5794 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5795 // All positions will have equal probability, but the current method will not provide a unique
5796 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5797 #define DARK 1
5798 #define LITE 2
5799 #define ANY 3
5800
5801 int squaresLeft[4];
5802 int piecesLeft[(int)BlackPawn];
5803 int seed, nrOfShuffles;
5804
5805 void
5806 GetPositionNumber ()
5807 {       // sets global variable seed
5808         int i;
5809
5810         seed = appData.defaultFrcPosition;
5811         if(seed < 0) { // randomize based on time for negative FRC position numbers
5812                 for(i=0; i<50; i++) seed += random();
5813                 seed = random() ^ random() >> 8 ^ random() << 8;
5814                 if(seed<0) seed = -seed;
5815         }
5816 }
5817
5818 int
5819 put (Board board, int pieceType, int rank, int n, int shade)
5820 // put the piece on the (n-1)-th empty squares of the given shade
5821 {
5822         int i;
5823
5824         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5825                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5826                         board[rank][i] = (ChessSquare) pieceType;
5827                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5828                         squaresLeft[ANY]--;
5829                         piecesLeft[pieceType]--;
5830                         return i;
5831                 }
5832         }
5833         return -1;
5834 }
5835
5836
5837 void
5838 AddOnePiece (Board board, int pieceType, int rank, int shade)
5839 // calculate where the next piece goes, (any empty square), and put it there
5840 {
5841         int i;
5842
5843         i = seed % squaresLeft[shade];
5844         nrOfShuffles *= squaresLeft[shade];
5845         seed /= squaresLeft[shade];
5846         put(board, pieceType, rank, i, shade);
5847 }
5848
5849 void
5850 AddTwoPieces (Board board, int pieceType, int rank)
5851 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5852 {
5853         int i, n=squaresLeft[ANY], j=n-1, k;
5854
5855         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5856         i = seed % k;  // pick one
5857         nrOfShuffles *= k;
5858         seed /= k;
5859         while(i >= j) i -= j--;
5860         j = n - 1 - j; i += j;
5861         put(board, pieceType, rank, j, ANY);
5862         put(board, pieceType, rank, i, ANY);
5863 }
5864
5865 void
5866 SetUpShuffle (Board board, int number)
5867 {
5868         int i, p, first=1;
5869
5870         GetPositionNumber(); nrOfShuffles = 1;
5871
5872         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5873         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5874         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5875
5876         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5877
5878         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5879             p = (int) board[0][i];
5880             if(p < (int) BlackPawn) piecesLeft[p] ++;
5881             board[0][i] = EmptySquare;
5882         }
5883
5884         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5885             // shuffles restricted to allow normal castling put KRR first
5886             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5887                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5888             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5889                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5890             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5891                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5892             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5893                 put(board, WhiteRook, 0, 0, ANY);
5894             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5895         }
5896
5897         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5898             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5899             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5900                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5901                 while(piecesLeft[p] >= 2) {
5902                     AddOnePiece(board, p, 0, LITE);
5903                     AddOnePiece(board, p, 0, DARK);
5904                 }
5905                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5906             }
5907
5908         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5909             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5910             // but we leave King and Rooks for last, to possibly obey FRC restriction
5911             if(p == (int)WhiteRook) continue;
5912             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5913             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5914         }
5915
5916         // now everything is placed, except perhaps King (Unicorn) and Rooks
5917
5918         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5919             // Last King gets castling rights
5920             while(piecesLeft[(int)WhiteUnicorn]) {
5921                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5922                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5923             }
5924
5925             while(piecesLeft[(int)WhiteKing]) {
5926                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5927                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5928             }
5929
5930
5931         } else {
5932             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5933             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5934         }
5935
5936         // Only Rooks can be left; simply place them all
5937         while(piecesLeft[(int)WhiteRook]) {
5938                 i = put(board, WhiteRook, 0, 0, ANY);
5939                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5940                         if(first) {
5941                                 first=0;
5942                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5943                         }
5944                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5945                 }
5946         }
5947         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5948             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5949         }
5950
5951         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5952 }
5953
5954 int
5955 ptclen (const char *s, char *escapes)
5956 {
5957     int n = 0;
5958     if(!*escapes) return strlen(s);
5959     while(*s) n += (*s != '/' && !strchr(escapes, *s)), s++;
5960     return n;
5961 }
5962
5963 int
5964 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
5965 /* [HGM] moved here from winboard.c because of its general usefulness */
5966 /*       Basically a safe strcpy that uses the last character as King */
5967 {
5968     int result = FALSE; int NrPieces, offs;
5969
5970     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
5971                     && NrPieces >= 12 && !(NrPieces&1)) {
5972         int i, j = 0; /* [HGM] Accept even length from 12 to 88 */
5973
5974         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5975         for( i=offs=0; i<NrPieces/2-1; i++ ) {
5976             char *p;
5977             if(map[j] == '/' && *escapes) offs = WhiteTokin - i, j++;
5978             table[i + offs] = map[j++];
5979             if(p = strchr(escapes, map[j])) j++, table[i + offs] += 64*(p - escapes + 1);
5980         }
5981         table[(int) WhiteKing]  = map[j++];
5982         for( i=offs=0; i<NrPieces/2-1; i++ ) {
5983             char *p;
5984             if(map[j] == '/' && *escapes) offs = WhiteTokin - i, j++;
5985             table[WHITE_TO_BLACK i + offs] = map[j++];
5986             if(p = strchr(escapes, map[j])) j++, table[WHITE_TO_BLACK i + offs] += 64*(p - escapes + 1);
5987         }
5988         table[(int) BlackKing]  = map[j++];
5989
5990         result = TRUE;
5991     }
5992
5993     return result;
5994 }
5995
5996 int
5997 SetCharTable (unsigned char *table, const char * map)
5998 {
5999     return SetCharTableEsc(table, map, "");
6000 }
6001
6002 void
6003 Prelude (Board board)
6004 {       // [HGM] superchess: random selection of exo-pieces
6005         int i, j, k; ChessSquare p;
6006         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6007
6008         GetPositionNumber(); // use FRC position number
6009
6010         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6011             SetCharTable(pieceToChar, appData.pieceToCharTable);
6012             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6013                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6014         }
6015
6016         j = seed%4;                 seed /= 4;
6017         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6018         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6019         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6020         j = seed%3 + (seed%3 >= j); seed /= 3;
6021         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6022         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6023         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6024         j = seed%3;                 seed /= 3;
6025         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6026         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6027         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6028         j = seed%2 + (seed%2 >= j); seed /= 2;
6029         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6030         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6031         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6032         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6033         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6034         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6035         put(board, exoPieces[0],    0, 0, ANY);
6036         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6037 }
6038
6039 void
6040 InitPosition (int redraw)
6041 {
6042     ChessSquare (* pieces)[BOARD_FILES];
6043     int i, j, pawnRow=1, pieceRows=1, overrule,
6044     oldx = gameInfo.boardWidth,
6045     oldy = gameInfo.boardHeight,
6046     oldh = gameInfo.holdingsWidth;
6047     static int oldv;
6048
6049     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6050
6051     /* [AS] Initialize pv info list [HGM] and game status */
6052     {
6053         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6054             pvInfoList[i].depth = 0;
6055             boards[i][EP_STATUS] = EP_NONE;
6056             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6057         }
6058
6059         initialRulePlies = 0; /* 50-move counter start */
6060
6061         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6062         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6063     }
6064
6065
6066     /* [HGM] logic here is completely changed. In stead of full positions */
6067     /* the initialized data only consist of the two backranks. The switch */
6068     /* selects which one we will use, which is than copied to the Board   */
6069     /* initialPosition, which for the rest is initialized by Pawns and    */
6070     /* empty squares. This initial position is then copied to boards[0],  */
6071     /* possibly after shuffling, so that it remains available.            */
6072
6073     gameInfo.holdingsWidth = 0; /* default board sizes */
6074     gameInfo.boardWidth    = 8;
6075     gameInfo.boardHeight   = 8;
6076     gameInfo.holdingsSize  = 0;
6077     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6078     for(i=0; i<BOARD_FILES-6; i++)
6079       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6080     initialPosition[EP_STATUS] = EP_NONE;
6081     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6082     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6083     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6084          SetCharTable(pieceNickName, appData.pieceNickNames);
6085     else SetCharTable(pieceNickName, "............");
6086     pieces = FIDEArray;
6087
6088     switch (gameInfo.variant) {
6089     case VariantFischeRandom:
6090       shuffleOpenings = TRUE;
6091       appData.fischerCastling = TRUE;
6092     default:
6093       break;
6094     case VariantShatranj:
6095       pieces = ShatranjArray;
6096       nrCastlingRights = 0;
6097       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6098       break;
6099     case VariantMakruk:
6100       pieces = makrukArray;
6101       nrCastlingRights = 0;
6102       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6103       break;
6104     case VariantASEAN:
6105       pieces = aseanArray;
6106       nrCastlingRights = 0;
6107       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6108       break;
6109     case VariantTwoKings:
6110       pieces = twoKingsArray;
6111       break;
6112     case VariantGrand:
6113       pieces = GrandArray;
6114       nrCastlingRights = 0;
6115       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6116       gameInfo.boardWidth = 10;
6117       gameInfo.boardHeight = 10;
6118       gameInfo.holdingsSize = 7;
6119       break;
6120     case VariantCapaRandom:
6121       shuffleOpenings = TRUE;
6122       appData.fischerCastling = TRUE;
6123     case VariantCapablanca:
6124       pieces = CapablancaArray;
6125       gameInfo.boardWidth = 10;
6126       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6127       break;
6128     case VariantGothic:
6129       pieces = GothicArray;
6130       gameInfo.boardWidth = 10;
6131       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6132       break;
6133     case VariantSChess:
6134       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6135       gameInfo.holdingsSize = 7;
6136       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6137       break;
6138     case VariantJanus:
6139       pieces = JanusArray;
6140       gameInfo.boardWidth = 10;
6141       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6142       nrCastlingRights = 6;
6143         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6144         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6145         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6146         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6147         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6148         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6149       break;
6150     case VariantFalcon:
6151       pieces = FalconArray;
6152       gameInfo.boardWidth = 10;
6153       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6154       break;
6155     case VariantXiangqi:
6156       pieces = XiangqiArray;
6157       gameInfo.boardWidth  = 9;
6158       gameInfo.boardHeight = 10;
6159       nrCastlingRights = 0;
6160       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6161       break;
6162     case VariantShogi:
6163       pieces = ShogiArray;
6164       gameInfo.boardWidth  = 9;
6165       gameInfo.boardHeight = 9;
6166       gameInfo.holdingsSize = 7;
6167       nrCastlingRights = 0;
6168       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6169       break;
6170     case VariantChu:
6171       pieces = ChuArray; pieceRows = 3;
6172       gameInfo.boardWidth  = 12;
6173       gameInfo.boardHeight = 12;
6174       nrCastlingRights = 0;
6175       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN/+.++.++++++++++.+++++K"
6176                                    "p.brqsexogcathd.vmlifn/+.++.++++++++++.+++++k", SUFFIXES);
6177       break;
6178     case VariantCourier:
6179       pieces = CourierArray;
6180       gameInfo.boardWidth  = 12;
6181       nrCastlingRights = 0;
6182       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6183       break;
6184     case VariantKnightmate:
6185       pieces = KnightmateArray;
6186       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6187       break;
6188     case VariantSpartan:
6189       pieces = SpartanArray;
6190       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6191       break;
6192     case VariantLion:
6193       pieces = lionArray;
6194       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6195       break;
6196     case VariantChuChess:
6197       pieces = ChuChessArray;
6198       gameInfo.boardWidth = 10;
6199       gameInfo.boardHeight = 10;
6200       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6201       break;
6202     case VariantFairy:
6203       pieces = fairyArray;
6204       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6205       break;
6206     case VariantGreat:
6207       pieces = GreatArray;
6208       gameInfo.boardWidth = 10;
6209       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6210       gameInfo.holdingsSize = 8;
6211       break;
6212     case VariantSuper:
6213       pieces = FIDEArray;
6214       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6215       gameInfo.holdingsSize = 8;
6216       startedFromSetupPosition = TRUE;
6217       break;
6218     case VariantCrazyhouse:
6219     case VariantBughouse:
6220       pieces = FIDEArray;
6221       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6222       gameInfo.holdingsSize = 5;
6223       break;
6224     case VariantWildCastle:
6225       pieces = FIDEArray;
6226       /* !!?shuffle with kings guaranteed to be on d or e file */
6227       shuffleOpenings = 1;
6228       break;
6229     case VariantNoCastle:
6230       pieces = FIDEArray;
6231       nrCastlingRights = 0;
6232       /* !!?unconstrained back-rank shuffle */
6233       shuffleOpenings = 1;
6234       break;
6235     }
6236
6237     overrule = 0;
6238     if(appData.NrFiles >= 0) {
6239         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6240         gameInfo.boardWidth = appData.NrFiles;
6241     }
6242     if(appData.NrRanks >= 0) {
6243         gameInfo.boardHeight = appData.NrRanks;
6244     }
6245     if(appData.holdingsSize >= 0) {
6246         i = appData.holdingsSize;
6247         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6248         gameInfo.holdingsSize = i;
6249     }
6250     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6251     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6252         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6253
6254     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6255     if(pawnRow < 1) pawnRow = 1;
6256     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6257        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6258     if(gameInfo.variant == VariantChu) pawnRow = 3;
6259
6260     /* User pieceToChar list overrules defaults */
6261     if(appData.pieceToCharTable != NULL)
6262         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6263
6264     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6265
6266         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6267             s = (ChessSquare) 0; /* account holding counts in guard band */
6268         for( i=0; i<BOARD_HEIGHT; i++ )
6269             initialPosition[i][j] = s;
6270
6271         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6272         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6273         initialPosition[pawnRow][j] = WhitePawn;
6274         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6275         if(gameInfo.variant == VariantXiangqi) {
6276             if(j&1) {
6277                 initialPosition[pawnRow][j] =
6278                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6279                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6280                    initialPosition[2][j] = WhiteCannon;
6281                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6282                 }
6283             }
6284         }
6285         if(gameInfo.variant == VariantChu) {
6286              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6287                initialPosition[pawnRow+1][j] = WhiteCobra,
6288                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6289              for(i=1; i<pieceRows; i++) {
6290                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6291                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6292              }
6293         }
6294         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6295             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6296                initialPosition[0][j] = WhiteRook;
6297                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6298             }
6299         }
6300         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6301     }
6302     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6303     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6304
6305             j=BOARD_LEFT+1;
6306             initialPosition[1][j] = WhiteBishop;
6307             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6308             j=BOARD_RGHT-2;
6309             initialPosition[1][j] = WhiteRook;
6310             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6311     }
6312
6313     if( nrCastlingRights == -1) {
6314         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6315         /*       This sets default castling rights from none to normal corners   */
6316         /* Variants with other castling rights must set them themselves above    */
6317         nrCastlingRights = 6;
6318
6319         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6320         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6321         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6322         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6323         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6324         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6325      }
6326
6327      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6328      if(gameInfo.variant == VariantGreat) { // promotion commoners
6329         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6330         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6331         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6332         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6333      }
6334      if( gameInfo.variant == VariantSChess ) {
6335       initialPosition[1][0] = BlackMarshall;
6336       initialPosition[2][0] = BlackAngel;
6337       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6338       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6339       initialPosition[1][1] = initialPosition[2][1] =
6340       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6341      }
6342   if (appData.debugMode) {
6343     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6344   }
6345     if(shuffleOpenings) {
6346         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6347         startedFromSetupPosition = TRUE;
6348     }
6349     if(startedFromPositionFile) {
6350       /* [HGM] loadPos: use PositionFile for every new game */
6351       CopyBoard(initialPosition, filePosition);
6352       for(i=0; i<nrCastlingRights; i++)
6353           initialRights[i] = filePosition[CASTLING][i];
6354       startedFromSetupPosition = TRUE;
6355     }
6356
6357     CopyBoard(boards[0], initialPosition);
6358
6359     if(oldx != gameInfo.boardWidth ||
6360        oldy != gameInfo.boardHeight ||
6361        oldv != gameInfo.variant ||
6362        oldh != gameInfo.holdingsWidth
6363                                          )
6364             InitDrawingSizes(-2 ,0);
6365
6366     oldv = gameInfo.variant;
6367     if (redraw)
6368       DrawPosition(TRUE, boards[currentMove]);
6369 }
6370
6371 void
6372 SendBoard (ChessProgramState *cps, int moveNum)
6373 {
6374     char message[MSG_SIZ];
6375
6376     if (cps->useSetboard) {
6377       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6378       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6379       SendToProgram(message, cps);
6380       free(fen);
6381
6382     } else {
6383       ChessSquare *bp;
6384       int i, j, left=0, right=BOARD_WIDTH;
6385       /* Kludge to set black to move, avoiding the troublesome and now
6386        * deprecated "black" command.
6387        */
6388       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6389         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6390
6391       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6392
6393       SendToProgram("edit\n", cps);
6394       SendToProgram("#\n", cps);
6395       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6396         bp = &boards[moveNum][i][left];
6397         for (j = left; j < right; j++, bp++) {
6398           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6399           if ((int) *bp < (int) BlackPawn) {
6400             if(j == BOARD_RGHT+1)
6401                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6402             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6403             if(message[0] == '+' || message[0] == '~') {
6404               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6405                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6406                         AAA + j, ONE + i - '0');
6407             }
6408             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6409                 message[1] = BOARD_RGHT   - 1 - j + '1';
6410                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6411             }
6412             SendToProgram(message, cps);
6413           }
6414         }
6415       }
6416
6417       SendToProgram("c\n", cps);
6418       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6419         bp = &boards[moveNum][i][left];
6420         for (j = left; j < right; j++, bp++) {
6421           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6422           if (((int) *bp != (int) EmptySquare)
6423               && ((int) *bp >= (int) BlackPawn)) {
6424             if(j == BOARD_LEFT-2)
6425                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6426             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6427                     AAA + j, ONE + i - '0');
6428             if(message[0] == '+' || message[0] == '~') {
6429               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6430                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6431                         AAA + j, ONE + i - '0');
6432             }
6433             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6434                 message[1] = BOARD_RGHT   - 1 - j + '1';
6435                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6436             }
6437             SendToProgram(message, cps);
6438           }
6439         }
6440       }
6441
6442       SendToProgram(".\n", cps);
6443     }
6444     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6445 }
6446
6447 char exclusionHeader[MSG_SIZ];
6448 int exCnt, excludePtr;
6449 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6450 static Exclusion excluTab[200];
6451 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6452
6453 static void
6454 WriteMap (int s)
6455 {
6456     int j;
6457     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6458     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6459 }
6460
6461 static void
6462 ClearMap ()
6463 {
6464     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6465     excludePtr = 24; exCnt = 0;
6466     WriteMap(0);
6467 }
6468
6469 static void
6470 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6471 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6472     char buf[2*MOVE_LEN], *p;
6473     Exclusion *e = excluTab;
6474     int i;
6475     for(i=0; i<exCnt; i++)
6476         if(e[i].ff == fromX && e[i].fr == fromY &&
6477            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6478     if(i == exCnt) { // was not in exclude list; add it
6479         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6480         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6481             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6482             return; // abort
6483         }
6484         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6485         excludePtr++; e[i].mark = excludePtr++;
6486         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6487         exCnt++;
6488     }
6489     exclusionHeader[e[i].mark] = state;
6490 }
6491
6492 static int
6493 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6494 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6495     char buf[MSG_SIZ];
6496     int j, k;
6497     ChessMove moveType;
6498     if((signed char)promoChar == -1) { // kludge to indicate best move
6499         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6500             return 1; // if unparsable, abort
6501     }
6502     // update exclusion map (resolving toggle by consulting existing state)
6503     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6504     j = k%8; k >>= 3;
6505     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6506     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6507          excludeMap[k] |=   1<<j;
6508     else excludeMap[k] &= ~(1<<j);
6509     // update header
6510     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6511     // inform engine
6512     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6513     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6514     SendToBoth(buf);
6515     return (state == '+');
6516 }
6517
6518 static void
6519 ExcludeClick (int index)
6520 {
6521     int i, j;
6522     Exclusion *e = excluTab;
6523     if(index < 25) { // none, best or tail clicked
6524         if(index < 13) { // none: include all
6525             WriteMap(0); // clear map
6526             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6527             SendToBoth("include all\n"); // and inform engine
6528         } else if(index > 18) { // tail
6529             if(exclusionHeader[19] == '-') { // tail was excluded
6530                 SendToBoth("include all\n");
6531                 WriteMap(0); // clear map completely
6532                 // now re-exclude selected moves
6533                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6534                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6535             } else { // tail was included or in mixed state
6536                 SendToBoth("exclude all\n");
6537                 WriteMap(0xFF); // fill map completely
6538                 // now re-include selected moves
6539                 j = 0; // count them
6540                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6541                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6542                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6543             }
6544         } else { // best
6545             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6546         }
6547     } else {
6548         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6549             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6550             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6551             break;
6552         }
6553     }
6554 }
6555
6556 ChessSquare
6557 DefaultPromoChoice (int white)
6558 {
6559     ChessSquare result;
6560     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6561        gameInfo.variant == VariantMakruk)
6562         result = WhiteFerz; // no choice
6563     else if(gameInfo.variant == VariantASEAN)
6564         result = WhiteRook; // no choice
6565     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6566         result= WhiteKing; // in Suicide Q is the last thing we want
6567     else if(gameInfo.variant == VariantSpartan)
6568         result = white ? WhiteQueen : WhiteAngel;
6569     else result = WhiteQueen;
6570     if(!white) result = WHITE_TO_BLACK result;
6571     return result;
6572 }
6573
6574 static int autoQueen; // [HGM] oneclick
6575
6576 int
6577 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6578 {
6579     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6580     /* [HGM] add Shogi promotions */
6581     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6582     ChessSquare piece, partner;
6583     ChessMove moveType;
6584     Boolean premove;
6585
6586     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6587     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6588
6589     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6590       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6591         return FALSE;
6592
6593     piece = boards[currentMove][fromY][fromX];
6594     if(gameInfo.variant == VariantChu) {
6595         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6596         promotionZoneSize = BOARD_HEIGHT/3;
6597         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6598     } else if(gameInfo.variant == VariantShogi) {
6599         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6600         highestPromotingPiece = (int)WhiteAlfil;
6601     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6602         promotionZoneSize = 3;
6603     }
6604
6605     // Treat Lance as Pawn when it is not representing Amazon or Lance
6606     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6607         if(piece == WhiteLance) piece = WhitePawn; else
6608         if(piece == BlackLance) piece = BlackPawn;
6609     }
6610
6611     // next weed out all moves that do not touch the promotion zone at all
6612     if((int)piece >= BlackPawn) {
6613         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6614              return FALSE;
6615         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6616         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6617     } else {
6618         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6619            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6620         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6621              return FALSE;
6622     }
6623
6624     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6625
6626     // weed out mandatory Shogi promotions
6627     if(gameInfo.variant == VariantShogi) {
6628         if(piece >= BlackPawn) {
6629             if(toY == 0 && piece == BlackPawn ||
6630                toY == 0 && piece == BlackQueen ||
6631                toY <= 1 && piece == BlackKnight) {
6632                 *promoChoice = '+';
6633                 return FALSE;
6634             }
6635         } else {
6636             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6637                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6638                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6639                 *promoChoice = '+';
6640                 return FALSE;
6641             }
6642         }
6643     }
6644
6645     // weed out obviously illegal Pawn moves
6646     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6647         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6648         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6649         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6650         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6651         // note we are not allowed to test for valid (non-)capture, due to premove
6652     }
6653
6654     // we either have a choice what to promote to, or (in Shogi) whether to promote
6655     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6656        gameInfo.variant == VariantMakruk) {
6657         ChessSquare p=BlackFerz;  // no choice
6658         while(p < EmptySquare) {  //but make sure we use piece that exists
6659             *promoChoice = PieceToChar(p++);
6660             if(*promoChoice != '.') break;
6661         }
6662         return FALSE;
6663     }
6664     // no sense asking what we must promote to if it is going to explode...
6665     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6666         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6667         return FALSE;
6668     }
6669     // give caller the default choice even if we will not make it
6670     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6671     partner = piece; // pieces can promote if the pieceToCharTable says so
6672     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6673     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6674     if(        sweepSelect && gameInfo.variant != VariantGreat
6675                            && gameInfo.variant != VariantGrand
6676                            && gameInfo.variant != VariantSuper) return FALSE;
6677     if(autoQueen) return FALSE; // predetermined
6678
6679     // suppress promotion popup on illegal moves that are not premoves
6680     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6681               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6682     if(appData.testLegality && !premove) {
6683         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6684                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6685         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6686         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6687             return FALSE;
6688     }
6689
6690     return TRUE;
6691 }
6692
6693 int
6694 InPalace (int row, int column)
6695 {   /* [HGM] for Xiangqi */
6696     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6697          column < (BOARD_WIDTH + 4)/2 &&
6698          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6699     return FALSE;
6700 }
6701
6702 int
6703 PieceForSquare (int x, int y)
6704 {
6705   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6706      return -1;
6707   else
6708      return boards[currentMove][y][x];
6709 }
6710
6711 int
6712 OKToStartUserMove (int x, int y)
6713 {
6714     ChessSquare from_piece;
6715     int white_piece;
6716
6717     if (matchMode) return FALSE;
6718     if (gameMode == EditPosition) return TRUE;
6719
6720     if (x >= 0 && y >= 0)
6721       from_piece = boards[currentMove][y][x];
6722     else
6723       from_piece = EmptySquare;
6724
6725     if (from_piece == EmptySquare) return FALSE;
6726
6727     white_piece = (int)from_piece >= (int)WhitePawn &&
6728       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6729
6730     switch (gameMode) {
6731       case AnalyzeFile:
6732       case TwoMachinesPlay:
6733       case EndOfGame:
6734         return FALSE;
6735
6736       case IcsObserving:
6737       case IcsIdle:
6738         return FALSE;
6739
6740       case MachinePlaysWhite:
6741       case IcsPlayingBlack:
6742         if (appData.zippyPlay) return FALSE;
6743         if (white_piece) {
6744             DisplayMoveError(_("You are playing Black"));
6745             return FALSE;
6746         }
6747         break;
6748
6749       case MachinePlaysBlack:
6750       case IcsPlayingWhite:
6751         if (appData.zippyPlay) return FALSE;
6752         if (!white_piece) {
6753             DisplayMoveError(_("You are playing White"));
6754             return FALSE;
6755         }
6756         break;
6757
6758       case PlayFromGameFile:
6759             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6760       case EditGame:
6761         if (!white_piece && WhiteOnMove(currentMove)) {
6762             DisplayMoveError(_("It is White's turn"));
6763             return FALSE;
6764         }
6765         if (white_piece && !WhiteOnMove(currentMove)) {
6766             DisplayMoveError(_("It is Black's turn"));
6767             return FALSE;
6768         }
6769         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6770             /* Editing correspondence game history */
6771             /* Could disallow this or prompt for confirmation */
6772             cmailOldMove = -1;
6773         }
6774         break;
6775
6776       case BeginningOfGame:
6777         if (appData.icsActive) return FALSE;
6778         if (!appData.noChessProgram) {
6779             if (!white_piece) {
6780                 DisplayMoveError(_("You are playing White"));
6781                 return FALSE;
6782             }
6783         }
6784         break;
6785
6786       case Training:
6787         if (!white_piece && WhiteOnMove(currentMove)) {
6788             DisplayMoveError(_("It is White's turn"));
6789             return FALSE;
6790         }
6791         if (white_piece && !WhiteOnMove(currentMove)) {
6792             DisplayMoveError(_("It is Black's turn"));
6793             return FALSE;
6794         }
6795         break;
6796
6797       default:
6798       case IcsExamining:
6799         break;
6800     }
6801     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6802         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6803         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6804         && gameMode != AnalyzeFile && gameMode != Training) {
6805         DisplayMoveError(_("Displayed position is not current"));
6806         return FALSE;
6807     }
6808     return TRUE;
6809 }
6810
6811 Boolean
6812 OnlyMove (int *x, int *y, Boolean captures)
6813 {
6814     DisambiguateClosure cl;
6815     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6816     switch(gameMode) {
6817       case MachinePlaysBlack:
6818       case IcsPlayingWhite:
6819       case BeginningOfGame:
6820         if(!WhiteOnMove(currentMove)) return FALSE;
6821         break;
6822       case MachinePlaysWhite:
6823       case IcsPlayingBlack:
6824         if(WhiteOnMove(currentMove)) return FALSE;
6825         break;
6826       case EditGame:
6827         break;
6828       default:
6829         return FALSE;
6830     }
6831     cl.pieceIn = EmptySquare;
6832     cl.rfIn = *y;
6833     cl.ffIn = *x;
6834     cl.rtIn = -1;
6835     cl.ftIn = -1;
6836     cl.promoCharIn = NULLCHAR;
6837     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6838     if( cl.kind == NormalMove ||
6839         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6840         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6841         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6842       fromX = cl.ff;
6843       fromY = cl.rf;
6844       *x = cl.ft;
6845       *y = cl.rt;
6846       return TRUE;
6847     }
6848     if(cl.kind != ImpossibleMove) return FALSE;
6849     cl.pieceIn = EmptySquare;
6850     cl.rfIn = -1;
6851     cl.ffIn = -1;
6852     cl.rtIn = *y;
6853     cl.ftIn = *x;
6854     cl.promoCharIn = NULLCHAR;
6855     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6856     if( cl.kind == NormalMove ||
6857         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6858         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6859         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6860       fromX = cl.ff;
6861       fromY = cl.rf;
6862       *x = cl.ft;
6863       *y = cl.rt;
6864       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6865       return TRUE;
6866     }
6867     return FALSE;
6868 }
6869
6870 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6871 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6872 int lastLoadGameUseList = FALSE;
6873 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6874 ChessMove lastLoadGameStart = EndOfFile;
6875 int doubleClick;
6876 Boolean addToBookFlag;
6877
6878 void
6879 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6880 {
6881     ChessMove moveType;
6882     ChessSquare pup;
6883     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6884
6885     /* Check if the user is playing in turn.  This is complicated because we
6886        let the user "pick up" a piece before it is his turn.  So the piece he
6887        tried to pick up may have been captured by the time he puts it down!
6888        Therefore we use the color the user is supposed to be playing in this
6889        test, not the color of the piece that is currently on the starting
6890        square---except in EditGame mode, where the user is playing both
6891        sides; fortunately there the capture race can't happen.  (It can
6892        now happen in IcsExamining mode, but that's just too bad.  The user
6893        will get a somewhat confusing message in that case.)
6894        */
6895
6896     switch (gameMode) {
6897       case AnalyzeFile:
6898       case TwoMachinesPlay:
6899       case EndOfGame:
6900       case IcsObserving:
6901       case IcsIdle:
6902         /* We switched into a game mode where moves are not accepted,
6903            perhaps while the mouse button was down. */
6904         return;
6905
6906       case MachinePlaysWhite:
6907         /* User is moving for Black */
6908         if (WhiteOnMove(currentMove)) {
6909             DisplayMoveError(_("It is White's turn"));
6910             return;
6911         }
6912         break;
6913
6914       case MachinePlaysBlack:
6915         /* User is moving for White */
6916         if (!WhiteOnMove(currentMove)) {
6917             DisplayMoveError(_("It is Black's turn"));
6918             return;
6919         }
6920         break;
6921
6922       case PlayFromGameFile:
6923             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6924       case EditGame:
6925       case IcsExamining:
6926       case BeginningOfGame:
6927       case AnalyzeMode:
6928       case Training:
6929         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6930         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6931             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6932             /* User is moving for Black */
6933             if (WhiteOnMove(currentMove)) {
6934                 DisplayMoveError(_("It is White's turn"));
6935                 return;
6936             }
6937         } else {
6938             /* User is moving for White */
6939             if (!WhiteOnMove(currentMove)) {
6940                 DisplayMoveError(_("It is Black's turn"));
6941                 return;
6942             }
6943         }
6944         break;
6945
6946       case IcsPlayingBlack:
6947         /* User is moving for Black */
6948         if (WhiteOnMove(currentMove)) {
6949             if (!appData.premove) {
6950                 DisplayMoveError(_("It is White's turn"));
6951             } else if (toX >= 0 && toY >= 0) {
6952                 premoveToX = toX;
6953                 premoveToY = toY;
6954                 premoveFromX = fromX;
6955                 premoveFromY = fromY;
6956                 premovePromoChar = promoChar;
6957                 gotPremove = 1;
6958                 if (appData.debugMode)
6959                     fprintf(debugFP, "Got premove: fromX %d,"
6960                             "fromY %d, toX %d, toY %d\n",
6961                             fromX, fromY, toX, toY);
6962             }
6963             return;
6964         }
6965         break;
6966
6967       case IcsPlayingWhite:
6968         /* User is moving for White */
6969         if (!WhiteOnMove(currentMove)) {
6970             if (!appData.premove) {
6971                 DisplayMoveError(_("It is Black's turn"));
6972             } else if (toX >= 0 && toY >= 0) {
6973                 premoveToX = toX;
6974                 premoveToY = toY;
6975                 premoveFromX = fromX;
6976                 premoveFromY = fromY;
6977                 premovePromoChar = promoChar;
6978                 gotPremove = 1;
6979                 if (appData.debugMode)
6980                     fprintf(debugFP, "Got premove: fromX %d,"
6981                             "fromY %d, toX %d, toY %d\n",
6982                             fromX, fromY, toX, toY);
6983             }
6984             return;
6985         }
6986         break;
6987
6988       default:
6989         break;
6990
6991       case EditPosition:
6992         /* EditPosition, empty square, or different color piece;
6993            click-click move is possible */
6994         if (toX == -2 || toY == -2) {
6995             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
6996             DrawPosition(FALSE, boards[currentMove]);
6997             return;
6998         } else if (toX >= 0 && toY >= 0) {
6999             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7000                 ChessSquare q, p = boards[0][rf][ff];
7001                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
7002                 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
7003                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
7004                 if(PieceToChar(q) == '+') gatingPiece = p;
7005             }
7006             boards[0][toY][toX] = boards[0][fromY][fromX];
7007             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7008                 if(boards[0][fromY][0] != EmptySquare) {
7009                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7010                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7011                 }
7012             } else
7013             if(fromX == BOARD_RGHT+1) {
7014                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7015                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7016                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7017                 }
7018             } else
7019             boards[0][fromY][fromX] = gatingPiece;
7020             DrawPosition(FALSE, boards[currentMove]);
7021             return;
7022         }
7023         return;
7024     }
7025
7026     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7027     pup = boards[currentMove][toY][toX];
7028
7029     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7030     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7031          if( pup != EmptySquare ) return;
7032          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7033            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7034                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7035            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7036            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7037            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7038            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7039          fromY = DROP_RANK;
7040     }
7041
7042     /* [HGM] always test for legality, to get promotion info */
7043     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7044                                          fromY, fromX, toY, toX, promoChar);
7045
7046     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7047
7048     /* [HGM] but possibly ignore an IllegalMove result */
7049     if (appData.testLegality) {
7050         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7051             DisplayMoveError(_("Illegal move"));
7052             return;
7053         }
7054     }
7055
7056     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7057         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7058              ClearPremoveHighlights(); // was included
7059         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7060         return;
7061     }
7062
7063     if(addToBookFlag) { // adding moves to book
7064         char buf[MSG_SIZ], move[MSG_SIZ];
7065         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7066         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');
7067         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7068         AddBookMove(buf);
7069         addToBookFlag = FALSE;
7070         ClearHighlights();
7071         return;
7072     }
7073
7074     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7075 }
7076
7077 /* Common tail of UserMoveEvent and DropMenuEvent */
7078 int
7079 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7080 {
7081     char *bookHit = 0;
7082
7083     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7084         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7085         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7086         if(WhiteOnMove(currentMove)) {
7087             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7088         } else {
7089             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7090         }
7091     }
7092
7093     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7094        move type in caller when we know the move is a legal promotion */
7095     if(moveType == NormalMove && promoChar)
7096         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7097
7098     /* [HGM] <popupFix> The following if has been moved here from
7099        UserMoveEvent(). Because it seemed to belong here (why not allow
7100        piece drops in training games?), and because it can only be
7101        performed after it is known to what we promote. */
7102     if (gameMode == Training) {
7103       /* compare the move played on the board to the next move in the
7104        * game. If they match, display the move and the opponent's response.
7105        * If they don't match, display an error message.
7106        */
7107       int saveAnimate;
7108       Board testBoard;
7109       CopyBoard(testBoard, boards[currentMove]);
7110       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7111
7112       if (CompareBoards(testBoard, boards[currentMove+1])) {
7113         ForwardInner(currentMove+1);
7114
7115         /* Autoplay the opponent's response.
7116          * if appData.animate was TRUE when Training mode was entered,
7117          * the response will be animated.
7118          */
7119         saveAnimate = appData.animate;
7120         appData.animate = animateTraining;
7121         ForwardInner(currentMove+1);
7122         appData.animate = saveAnimate;
7123
7124         /* check for the end of the game */
7125         if (currentMove >= forwardMostMove) {
7126           gameMode = PlayFromGameFile;
7127           ModeHighlight();
7128           SetTrainingModeOff();
7129           DisplayInformation(_("End of game"));
7130         }
7131       } else {
7132         DisplayError(_("Incorrect move"), 0);
7133       }
7134       return 1;
7135     }
7136
7137   /* Ok, now we know that the move is good, so we can kill
7138      the previous line in Analysis Mode */
7139   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7140                                 && currentMove < forwardMostMove) {
7141     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7142     else forwardMostMove = currentMove;
7143   }
7144
7145   ClearMap();
7146
7147   /* If we need the chess program but it's dead, restart it */
7148   ResurrectChessProgram();
7149
7150   /* A user move restarts a paused game*/
7151   if (pausing)
7152     PauseEvent();
7153
7154   thinkOutput[0] = NULLCHAR;
7155
7156   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7157
7158   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7159     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7160     return 1;
7161   }
7162
7163   if (gameMode == BeginningOfGame) {
7164     if (appData.noChessProgram) {
7165       gameMode = EditGame;
7166       SetGameInfo();
7167     } else {
7168       char buf[MSG_SIZ];
7169       gameMode = MachinePlaysBlack;
7170       StartClocks();
7171       SetGameInfo();
7172       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7173       DisplayTitle(buf);
7174       if (first.sendName) {
7175         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7176         SendToProgram(buf, &first);
7177       }
7178       StartClocks();
7179     }
7180     ModeHighlight();
7181   }
7182
7183   /* Relay move to ICS or chess engine */
7184   if (appData.icsActive) {
7185     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7186         gameMode == IcsExamining) {
7187       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7188         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7189         SendToICS("draw ");
7190         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7191       }
7192       // also send plain move, in case ICS does not understand atomic claims
7193       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7194       ics_user_moved = 1;
7195     }
7196   } else {
7197     if (first.sendTime && (gameMode == BeginningOfGame ||
7198                            gameMode == MachinePlaysWhite ||
7199                            gameMode == MachinePlaysBlack)) {
7200       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7201     }
7202     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7203          // [HGM] book: if program might be playing, let it use book
7204         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7205         first.maybeThinking = TRUE;
7206     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7207         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7208         SendBoard(&first, currentMove+1);
7209         if(second.analyzing) {
7210             if(!second.useSetboard) SendToProgram("undo\n", &second);
7211             SendBoard(&second, currentMove+1);
7212         }
7213     } else {
7214         SendMoveToProgram(forwardMostMove-1, &first);
7215         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7216     }
7217     if (currentMove == cmailOldMove + 1) {
7218       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7219     }
7220   }
7221
7222   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7223
7224   switch (gameMode) {
7225   case EditGame:
7226     if(appData.testLegality)
7227     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7228     case MT_NONE:
7229     case MT_CHECK:
7230       break;
7231     case MT_CHECKMATE:
7232     case MT_STAINMATE:
7233       if (WhiteOnMove(currentMove)) {
7234         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7235       } else {
7236         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7237       }
7238       break;
7239     case MT_STALEMATE:
7240       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7241       break;
7242     }
7243     break;
7244
7245   case MachinePlaysBlack:
7246   case MachinePlaysWhite:
7247     /* disable certain menu options while machine is thinking */
7248     SetMachineThinkingEnables();
7249     break;
7250
7251   default:
7252     break;
7253   }
7254
7255   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7256   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7257
7258   if(bookHit) { // [HGM] book: simulate book reply
7259         static char bookMove[MSG_SIZ]; // a bit generous?
7260
7261         programStats.nodes = programStats.depth = programStats.time =
7262         programStats.score = programStats.got_only_move = 0;
7263         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7264
7265         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7266         strcat(bookMove, bookHit);
7267         HandleMachineMove(bookMove, &first);
7268   }
7269   return 1;
7270 }
7271
7272 void
7273 MarkByFEN(char *fen)
7274 {
7275         int r, f;
7276         if(!appData.markers || !appData.highlightDragging) return;
7277         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7278         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7279         while(*fen) {
7280             int s = 0;
7281             marker[r][f] = 0;
7282             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7283             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7284             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7285             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7286             if(*fen == 'T') marker[r][f++] = 0; else
7287             if(*fen == 'Y') marker[r][f++] = 1; else
7288             if(*fen == 'G') marker[r][f++] = 3; else
7289             if(*fen == 'B') marker[r][f++] = 4; else
7290             if(*fen == 'C') marker[r][f++] = 5; else
7291             if(*fen == 'M') marker[r][f++] = 6; else
7292             if(*fen == 'W') marker[r][f++] = 7; else
7293             if(*fen == 'D') marker[r][f++] = 8; else
7294             if(*fen == 'R') marker[r][f++] = 2; else {
7295                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7296               f += s; fen -= s>0;
7297             }
7298             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7299             if(r < 0) break;
7300             fen++;
7301         }
7302         DrawPosition(TRUE, NULL);
7303 }
7304
7305 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7306
7307 void
7308 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7309 {
7310     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7311     Markers *m = (Markers *) closure;
7312     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7313         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7314                          || kind == WhiteCapturesEnPassant
7315                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 1;
7316     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 1;
7317 }
7318
7319 static int hoverSavedValid;
7320
7321 void
7322 MarkTargetSquares (int clear)
7323 {
7324   int x, y, sum=0;
7325   if(clear) { // no reason to ever suppress clearing
7326     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7327     hoverSavedValid = 0;
7328     if(!sum) return; // nothing was cleared,no redraw needed
7329   } else {
7330     int capt = 0;
7331     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7332        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7333     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7334     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7335       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7336       if(capt)
7337       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7338     }
7339   }
7340   DrawPosition(FALSE, NULL);
7341 }
7342
7343 int
7344 Explode (Board board, int fromX, int fromY, int toX, int toY)
7345 {
7346     if(gameInfo.variant == VariantAtomic &&
7347        (board[toY][toX] != EmptySquare ||                     // capture?
7348         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7349                          board[fromY][fromX] == BlackPawn   )
7350       )) {
7351         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7352         return TRUE;
7353     }
7354     return FALSE;
7355 }
7356
7357 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7358
7359 int
7360 CanPromote (ChessSquare piece, int y)
7361 {
7362         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7363         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7364         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7365         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7366            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7367            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7368          gameInfo.variant == VariantMakruk) return FALSE;
7369         return (piece == BlackPawn && y <= zone ||
7370                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7371                 piece == BlackLance && y <= zone ||
7372                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7373 }
7374
7375 void
7376 HoverEvent (int xPix, int yPix, int x, int y)
7377 {
7378         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7379         int r, f;
7380         if(!first.highlight) return;
7381         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7382         if(x == oldX && y == oldY) return; // only do something if we enter new square
7383         oldFromX = fromX; oldFromY = fromY;
7384         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7385           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7386             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7387           hoverSavedValid = 1;
7388         } else if(oldX != x || oldY != y) {
7389           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7390           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7391           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7392             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7393           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7394             char buf[MSG_SIZ];
7395             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7396             SendToProgram(buf, &first);
7397           }
7398           oldX = x; oldY = y;
7399 //        SetHighlights(fromX, fromY, x, y);
7400         }
7401 }
7402
7403 void ReportClick(char *action, int x, int y)
7404 {
7405         char buf[MSG_SIZ]; // Inform engine of what user does
7406         int r, f;
7407         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7408           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7409             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7410         if(!first.highlight || gameMode == EditPosition) return;
7411         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7412         SendToProgram(buf, &first);
7413 }
7414
7415 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7416
7417 void
7418 LeftClick (ClickType clickType, int xPix, int yPix)
7419 {
7420     int x, y;
7421     Boolean saveAnimate;
7422     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7423     char promoChoice = NULLCHAR;
7424     ChessSquare piece;
7425     static TimeMark lastClickTime, prevClickTime;
7426
7427     x = EventToSquare(xPix, BOARD_WIDTH);
7428     y = EventToSquare(yPix, BOARD_HEIGHT);
7429     if (!flipView && y >= 0) {
7430         y = BOARD_HEIGHT - 1 - y;
7431     }
7432     if (flipView && x >= 0) {
7433         x = BOARD_WIDTH - 1 - x;
7434     }
7435
7436     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7437         static int dummy;
7438         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7439         right = TRUE;
7440         return;
7441     }
7442
7443     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7444
7445     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7446
7447     if (clickType == Press) ErrorPopDown();
7448     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7449
7450     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7451         defaultPromoChoice = promoSweep;
7452         promoSweep = EmptySquare;   // terminate sweep
7453         promoDefaultAltered = TRUE;
7454         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7455     }
7456
7457     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7458         if(clickType == Release) return; // ignore upclick of click-click destination
7459         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7460         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7461         if(gameInfo.holdingsWidth &&
7462                 (WhiteOnMove(currentMove)
7463                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7464                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7465             // click in right holdings, for determining promotion piece
7466             ChessSquare p = boards[currentMove][y][x];
7467             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7468             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7469             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7470                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7471                 fromX = fromY = -1;
7472                 return;
7473             }
7474         }
7475         DrawPosition(FALSE, boards[currentMove]);
7476         return;
7477     }
7478
7479     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7480     if(clickType == Press
7481             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7482               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7483               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7484         return;
7485
7486     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7487         // could be static click on premove from-square: abort premove
7488         gotPremove = 0;
7489         ClearPremoveHighlights();
7490     }
7491
7492     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7493         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7494
7495     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7496         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7497                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7498         defaultPromoChoice = DefaultPromoChoice(side);
7499     }
7500
7501     autoQueen = appData.alwaysPromoteToQueen;
7502
7503     if (fromX == -1) {
7504       int originalY = y;
7505       gatingPiece = EmptySquare;
7506       if (clickType != Press) {
7507         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7508             DragPieceEnd(xPix, yPix); dragging = 0;
7509             DrawPosition(FALSE, NULL);
7510         }
7511         return;
7512       }
7513       doubleClick = FALSE;
7514       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7515         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7516       }
7517       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7518       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7519          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7520          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7521             /* First square */
7522             if (OKToStartUserMove(fromX, fromY)) {
7523                 second = 0;
7524                 ReportClick("lift", x, y);
7525                 MarkTargetSquares(0);
7526                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7527                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7528                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7529                     promoSweep = defaultPromoChoice;
7530                     selectFlag = 0; lastX = xPix; lastY = yPix;
7531                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7532                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7533                 }
7534                 if (appData.highlightDragging) {
7535                     SetHighlights(fromX, fromY, -1, -1);
7536                 } else {
7537                     ClearHighlights();
7538                 }
7539             } else fromX = fromY = -1;
7540             return;
7541         }
7542     }
7543
7544     /* fromX != -1 */
7545     if (clickType == Press && gameMode != EditPosition) {
7546         ChessSquare fromP;
7547         ChessSquare toP;
7548         int frc;
7549
7550         // ignore off-board to clicks
7551         if(y < 0 || x < 0) return;
7552
7553         /* Check if clicking again on the same color piece */
7554         fromP = boards[currentMove][fromY][fromX];
7555         toP = boards[currentMove][y][x];
7556         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7557         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7558             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7559            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7560              WhitePawn <= toP && toP <= WhiteKing &&
7561              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7562              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7563             (BlackPawn <= fromP && fromP <= BlackKing &&
7564              BlackPawn <= toP && toP <= BlackKing &&
7565              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7566              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7567             /* Clicked again on same color piece -- changed his mind */
7568             second = (x == fromX && y == fromY);
7569             killX = killY = -1;
7570             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7571                 second = FALSE; // first double-click rather than scond click
7572                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7573             }
7574             promoDefaultAltered = FALSE;
7575             MarkTargetSquares(1);
7576            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7577             if (appData.highlightDragging) {
7578                 SetHighlights(x, y, -1, -1);
7579             } else {
7580                 ClearHighlights();
7581             }
7582             if (OKToStartUserMove(x, y)) {
7583                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7584                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7585                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7586                  gatingPiece = boards[currentMove][fromY][fromX];
7587                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7588                 fromX = x;
7589                 fromY = y; dragging = 1;
7590                 if(!second) ReportClick("lift", x, y);
7591                 MarkTargetSquares(0);
7592                 DragPieceBegin(xPix, yPix, FALSE);
7593                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7594                     promoSweep = defaultPromoChoice;
7595                     selectFlag = 0; lastX = xPix; lastY = yPix;
7596                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7597                 }
7598             }
7599            }
7600            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7601            second = FALSE;
7602         }
7603         // ignore clicks on holdings
7604         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7605     }
7606
7607     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7608         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7609         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7610         return;
7611     }
7612
7613     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7614         DragPieceEnd(xPix, yPix); dragging = 0;
7615         if(clearFlag) {
7616             // a deferred attempt to click-click move an empty square on top of a piece
7617             boards[currentMove][y][x] = EmptySquare;
7618             ClearHighlights();
7619             DrawPosition(FALSE, boards[currentMove]);
7620             fromX = fromY = -1; clearFlag = 0;
7621             return;
7622         }
7623         if (appData.animateDragging) {
7624             /* Undo animation damage if any */
7625             DrawPosition(FALSE, NULL);
7626         }
7627         if (second) {
7628             /* Second up/down in same square; just abort move */
7629             second = 0;
7630             fromX = fromY = -1;
7631             gatingPiece = EmptySquare;
7632             MarkTargetSquares(1);
7633             ClearHighlights();
7634             gotPremove = 0;
7635             ClearPremoveHighlights();
7636         } else {
7637             /* First upclick in same square; start click-click mode */
7638             SetHighlights(x, y, -1, -1);
7639         }
7640         return;
7641     }
7642
7643     clearFlag = 0;
7644
7645     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7646        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7647         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7648         DisplayMessage(_("only marked squares are legal"),"");
7649         DrawPosition(TRUE, NULL);
7650         return; // ignore to-click
7651     }
7652
7653     /* we now have a different from- and (possibly off-board) to-square */
7654     /* Completed move */
7655     if(!sweepSelecting) {
7656         toX = x;
7657         toY = y;
7658     }
7659
7660     piece = boards[currentMove][fromY][fromX];
7661
7662     saveAnimate = appData.animate;
7663     if (clickType == Press) {
7664         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7665         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7666             // must be Edit Position mode with empty-square selected
7667             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7668             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7669             return;
7670         }
7671         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7672             return;
7673         }
7674         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7675             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7676         } else
7677         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7678         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7679           if(appData.sweepSelect) {
7680             promoSweep = defaultPromoChoice;
7681             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7682             selectFlag = 0; lastX = xPix; lastY = yPix;
7683             Sweep(0); // Pawn that is going to promote: preview promotion piece
7684             sweepSelecting = 1;
7685             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7686             MarkTargetSquares(1);
7687           }
7688           return; // promo popup appears on up-click
7689         }
7690         /* Finish clickclick move */
7691         if (appData.animate || appData.highlightLastMove) {
7692             SetHighlights(fromX, fromY, toX, toY);
7693         } else {
7694             ClearHighlights();
7695         }
7696     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7697         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7698         if (appData.animate || appData.highlightLastMove) {
7699             SetHighlights(fromX, fromY, toX, toY);
7700         } else {
7701             ClearHighlights();
7702         }
7703     } else {
7704 #if 0
7705 // [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
7706         /* Finish drag move */
7707         if (appData.highlightLastMove) {
7708             SetHighlights(fromX, fromY, toX, toY);
7709         } else {
7710             ClearHighlights();
7711         }
7712 #endif
7713         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7714         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7715           dragging *= 2;            // flag button-less dragging if we are dragging
7716           MarkTargetSquares(1);
7717           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7718           else {
7719             kill2X = killX; kill2Y = killY;
7720             killX = x; killY = y;     //remeber this square as intermediate
7721             ReportClick("put", x, y); // and inform engine
7722             ReportClick("lift", x, y);
7723             MarkTargetSquares(0);
7724             return;
7725           }
7726         }
7727         DragPieceEnd(xPix, yPix); dragging = 0;
7728         /* Don't animate move and drag both */
7729         appData.animate = FALSE;
7730     }
7731
7732     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7733     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7734         ChessSquare piece = boards[currentMove][fromY][fromX];
7735         if(gameMode == EditPosition && piece != EmptySquare &&
7736            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7737             int n;
7738
7739             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7740                 n = PieceToNumber(piece - (int)BlackPawn);
7741                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7742                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7743                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7744             } else
7745             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7746                 n = PieceToNumber(piece);
7747                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7748                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7749                 boards[currentMove][n][BOARD_WIDTH-2]++;
7750             }
7751             boards[currentMove][fromY][fromX] = EmptySquare;
7752         }
7753         ClearHighlights();
7754         fromX = fromY = -1;
7755         MarkTargetSquares(1);
7756         DrawPosition(TRUE, boards[currentMove]);
7757         return;
7758     }
7759
7760     // off-board moves should not be highlighted
7761     if(x < 0 || y < 0) ClearHighlights();
7762     else ReportClick("put", x, y);
7763
7764     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7765
7766     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7767
7768     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7769         SetHighlights(fromX, fromY, toX, toY);
7770         MarkTargetSquares(1);
7771         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7772             // [HGM] super: promotion to captured piece selected from holdings
7773             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7774             promotionChoice = TRUE;
7775             // kludge follows to temporarily execute move on display, without promoting yet
7776             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7777             boards[currentMove][toY][toX] = p;
7778             DrawPosition(FALSE, boards[currentMove]);
7779             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7780             boards[currentMove][toY][toX] = q;
7781             DisplayMessage("Click in holdings to choose piece", "");
7782             return;
7783         }
7784         PromotionPopUp(promoChoice);
7785     } else {
7786         int oldMove = currentMove;
7787         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7788         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7789         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7790         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7791            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7792             DrawPosition(TRUE, boards[currentMove]);
7793         MarkTargetSquares(1);
7794         fromX = fromY = -1;
7795     }
7796     appData.animate = saveAnimate;
7797     if (appData.animate || appData.animateDragging) {
7798         /* Undo animation damage if needed */
7799         DrawPosition(FALSE, NULL);
7800     }
7801 }
7802
7803 int
7804 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7805 {   // front-end-free part taken out of PieceMenuPopup
7806     int whichMenu; int xSqr, ySqr;
7807
7808     if(seekGraphUp) { // [HGM] seekgraph
7809         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7810         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7811         return -2;
7812     }
7813
7814     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7815          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7816         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7817         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7818         if(action == Press)   {
7819             originalFlip = flipView;
7820             flipView = !flipView; // temporarily flip board to see game from partners perspective
7821             DrawPosition(TRUE, partnerBoard);
7822             DisplayMessage(partnerStatus, "");
7823             partnerUp = TRUE;
7824         } else if(action == Release) {
7825             flipView = originalFlip;
7826             DrawPosition(TRUE, boards[currentMove]);
7827             partnerUp = FALSE;
7828         }
7829         return -2;
7830     }
7831
7832     xSqr = EventToSquare(x, BOARD_WIDTH);
7833     ySqr = EventToSquare(y, BOARD_HEIGHT);
7834     if (action == Release) {
7835         if(pieceSweep != EmptySquare) {
7836             EditPositionMenuEvent(pieceSweep, toX, toY);
7837             pieceSweep = EmptySquare;
7838         } else UnLoadPV(); // [HGM] pv
7839     }
7840     if (action != Press) return -2; // return code to be ignored
7841     switch (gameMode) {
7842       case IcsExamining:
7843         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7844       case EditPosition:
7845         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7846         if (xSqr < 0 || ySqr < 0) return -1;
7847         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7848         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7849         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7850         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7851         NextPiece(0);
7852         return 2; // grab
7853       case IcsObserving:
7854         if(!appData.icsEngineAnalyze) return -1;
7855       case IcsPlayingWhite:
7856       case IcsPlayingBlack:
7857         if(!appData.zippyPlay) goto noZip;
7858       case AnalyzeMode:
7859       case AnalyzeFile:
7860       case MachinePlaysWhite:
7861       case MachinePlaysBlack:
7862       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7863         if (!appData.dropMenu) {
7864           LoadPV(x, y);
7865           return 2; // flag front-end to grab mouse events
7866         }
7867         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7868            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7869       case EditGame:
7870       noZip:
7871         if (xSqr < 0 || ySqr < 0) return -1;
7872         if (!appData.dropMenu || appData.testLegality &&
7873             gameInfo.variant != VariantBughouse &&
7874             gameInfo.variant != VariantCrazyhouse) return -1;
7875         whichMenu = 1; // drop menu
7876         break;
7877       default:
7878         return -1;
7879     }
7880
7881     if (((*fromX = xSqr) < 0) ||
7882         ((*fromY = ySqr) < 0)) {
7883         *fromX = *fromY = -1;
7884         return -1;
7885     }
7886     if (flipView)
7887       *fromX = BOARD_WIDTH - 1 - *fromX;
7888     else
7889       *fromY = BOARD_HEIGHT - 1 - *fromY;
7890
7891     return whichMenu;
7892 }
7893
7894 void
7895 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7896 {
7897 //    char * hint = lastHint;
7898     FrontEndProgramStats stats;
7899
7900     stats.which = cps == &first ? 0 : 1;
7901     stats.depth = cpstats->depth;
7902     stats.nodes = cpstats->nodes;
7903     stats.score = cpstats->score;
7904     stats.time = cpstats->time;
7905     stats.pv = cpstats->movelist;
7906     stats.hint = lastHint;
7907     stats.an_move_index = 0;
7908     stats.an_move_count = 0;
7909
7910     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7911         stats.hint = cpstats->move_name;
7912         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7913         stats.an_move_count = cpstats->nr_moves;
7914     }
7915
7916     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
7917
7918     SetProgramStats( &stats );
7919 }
7920
7921 void
7922 ClearEngineOutputPane (int which)
7923 {
7924     static FrontEndProgramStats dummyStats;
7925     dummyStats.which = which;
7926     dummyStats.pv = "#";
7927     SetProgramStats( &dummyStats );
7928 }
7929
7930 #define MAXPLAYERS 500
7931
7932 char *
7933 TourneyStandings (int display)
7934 {
7935     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7936     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7937     char result, *p, *names[MAXPLAYERS];
7938
7939     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7940         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7941     names[0] = p = strdup(appData.participants);
7942     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7943
7944     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7945
7946     while(result = appData.results[nr]) {
7947         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7948         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7949         wScore = bScore = 0;
7950         switch(result) {
7951           case '+': wScore = 2; break;
7952           case '-': bScore = 2; break;
7953           case '=': wScore = bScore = 1; break;
7954           case ' ':
7955           case '*': return strdup("busy"); // tourney not finished
7956         }
7957         score[w] += wScore;
7958         score[b] += bScore;
7959         games[w]++;
7960         games[b]++;
7961         nr++;
7962     }
7963     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7964     for(w=0; w<nPlayers; w++) {
7965         bScore = -1;
7966         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7967         ranking[w] = b; points[w] = bScore; score[b] = -2;
7968     }
7969     p = malloc(nPlayers*34+1);
7970     for(w=0; w<nPlayers && w<display; w++)
7971         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7972     free(names[0]);
7973     return p;
7974 }
7975
7976 void
7977 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7978 {       // count all piece types
7979         int p, f, r;
7980         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7981         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7982         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7983                 p = board[r][f];
7984                 pCnt[p]++;
7985                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7986                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7987                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7988                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7989                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7990                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7991         }
7992 }
7993
7994 int
7995 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7996 {
7997         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7998         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7999
8000         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8001         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8002         if(myPawns == 2 && nMine == 3) // KPP
8003             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8004         if(myPawns == 1 && nMine == 2) // KP
8005             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8006         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8007             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8008         if(myPawns) return FALSE;
8009         if(pCnt[WhiteRook+side])
8010             return pCnt[BlackRook-side] ||
8011                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8012                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8013                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8014         if(pCnt[WhiteCannon+side]) {
8015             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8016             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8017         }
8018         if(pCnt[WhiteKnight+side])
8019             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8020         return FALSE;
8021 }
8022
8023 int
8024 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8025 {
8026         VariantClass v = gameInfo.variant;
8027
8028         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8029         if(v == VariantShatranj) return TRUE; // always winnable through baring
8030         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8031         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8032
8033         if(v == VariantXiangqi) {
8034                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8035
8036                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8037                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8038                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8039                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8040                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8041                 if(stale) // we have at least one last-rank P plus perhaps C
8042                     return majors // KPKX
8043                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8044                 else // KCA*E*
8045                     return pCnt[WhiteFerz+side] // KCAK
8046                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8047                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8048                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8049
8050         } else if(v == VariantKnightmate) {
8051                 if(nMine == 1) return FALSE;
8052                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8053         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8054                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8055
8056                 if(nMine == 1) return FALSE; // bare King
8057                 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
8058                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8059                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8060                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8061                 if(pCnt[WhiteKnight+side])
8062                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8063                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8064                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8065                 if(nBishops)
8066                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8067                 if(pCnt[WhiteAlfil+side])
8068                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8069                 if(pCnt[WhiteWazir+side])
8070                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8071         }
8072
8073         return TRUE;
8074 }
8075
8076 int
8077 CompareWithRights (Board b1, Board b2)
8078 {
8079     int rights = 0;
8080     if(!CompareBoards(b1, b2)) return FALSE;
8081     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8082     /* compare castling rights */
8083     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8084            rights++; /* King lost rights, while rook still had them */
8085     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8086         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8087            rights++; /* but at least one rook lost them */
8088     }
8089     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8090            rights++;
8091     if( b1[CASTLING][5] != NoRights ) {
8092         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8093            rights++;
8094     }
8095     return rights == 0;
8096 }
8097
8098 int
8099 Adjudicate (ChessProgramState *cps)
8100 {       // [HGM] some adjudications useful with buggy engines
8101         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8102         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8103         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8104         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8105         int k, drop, count = 0; static int bare = 1;
8106         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8107         Boolean canAdjudicate = !appData.icsActive;
8108
8109         // most tests only when we understand the game, i.e. legality-checking on
8110             if( appData.testLegality )
8111             {   /* [HGM] Some more adjudications for obstinate engines */
8112                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8113                 static int moveCount = 6;
8114                 ChessMove result;
8115                 char *reason = NULL;
8116
8117                 /* Count what is on board. */
8118                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8119
8120                 /* Some material-based adjudications that have to be made before stalemate test */
8121                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8122                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8123                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8124                      if(canAdjudicate && appData.checkMates) {
8125                          if(engineOpponent)
8126                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8127                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8128                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8129                          return 1;
8130                      }
8131                 }
8132
8133                 /* Bare King in Shatranj (loses) or Losers (wins) */
8134                 if( nrW == 1 || nrB == 1) {
8135                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8136                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8137                      if(canAdjudicate && appData.checkMates) {
8138                          if(engineOpponent)
8139                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8140                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8141                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8142                          return 1;
8143                      }
8144                   } else
8145                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8146                   {    /* bare King */
8147                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8148                         if(canAdjudicate && appData.checkMates) {
8149                             /* but only adjudicate if adjudication enabled */
8150                             if(engineOpponent)
8151                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8152                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8153                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8154                             return 1;
8155                         }
8156                   }
8157                 } else bare = 1;
8158
8159
8160             // don't wait for engine to announce game end if we can judge ourselves
8161             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8162               case MT_CHECK:
8163                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8164                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8165                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8166                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8167                             checkCnt++;
8168                         if(checkCnt >= 2) {
8169                             reason = "Xboard adjudication: 3rd check";
8170                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8171                             break;
8172                         }
8173                     }
8174                 }
8175               case MT_NONE:
8176               default:
8177                 break;
8178               case MT_STEALMATE:
8179               case MT_STALEMATE:
8180               case MT_STAINMATE:
8181                 reason = "Xboard adjudication: Stalemate";
8182                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8183                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8184                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8185                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8186                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8187                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8188                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8189                                                                         EP_CHECKMATE : EP_WINS);
8190                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8191                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8192                 }
8193                 break;
8194               case MT_CHECKMATE:
8195                 reason = "Xboard adjudication: Checkmate";
8196                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8197                 if(gameInfo.variant == VariantShogi) {
8198                     if(forwardMostMove > backwardMostMove
8199                        && moveList[forwardMostMove-1][1] == '@'
8200                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8201                         reason = "XBoard adjudication: pawn-drop mate";
8202                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8203                     }
8204                 }
8205                 break;
8206             }
8207
8208                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8209                     case EP_STALEMATE:
8210                         result = GameIsDrawn; break;
8211                     case EP_CHECKMATE:
8212                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8213                     case EP_WINS:
8214                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8215                     default:
8216                         result = EndOfFile;
8217                 }
8218                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8219                     if(engineOpponent)
8220                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8221                     GameEnds( result, reason, GE_XBOARD );
8222                     return 1;
8223                 }
8224
8225                 /* Next absolutely insufficient mating material. */
8226                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8227                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8228                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8229
8230                      /* always flag draws, for judging claims */
8231                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8232
8233                      if(canAdjudicate && appData.materialDraws) {
8234                          /* but only adjudicate them if adjudication enabled */
8235                          if(engineOpponent) {
8236                            SendToProgram("force\n", engineOpponent); // suppress reply
8237                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8238                          }
8239                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8240                          return 1;
8241                      }
8242                 }
8243
8244                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8245                 if(gameInfo.variant == VariantXiangqi ?
8246                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8247                  : nrW + nrB == 4 &&
8248                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8249                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8250                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8251                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8252                    ) ) {
8253                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8254                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8255                           if(engineOpponent) {
8256                             SendToProgram("force\n", engineOpponent); // suppress reply
8257                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8258                           }
8259                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8260                           return 1;
8261                      }
8262                 } else moveCount = 6;
8263             }
8264
8265         // Repetition draws and 50-move rule can be applied independently of legality testing
8266
8267                 /* Check for rep-draws */
8268                 count = 0;
8269                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8270                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8271                 for(k = forwardMostMove-2;
8272                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8273                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8274                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8275                     k-=2)
8276                 {   int rights=0;
8277                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8278                         /* compare castling rights */
8279                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8280                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8281                                 rights++; /* King lost rights, while rook still had them */
8282                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8283                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8284                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8285                                    rights++; /* but at least one rook lost them */
8286                         }
8287                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8288                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8289                                 rights++;
8290                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8291                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8292                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8293                                    rights++;
8294                         }
8295                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8296                             && appData.drawRepeats > 1) {
8297                              /* adjudicate after user-specified nr of repeats */
8298                              int result = GameIsDrawn;
8299                              char *details = "XBoard adjudication: repetition draw";
8300                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8301                                 // [HGM] xiangqi: check for forbidden perpetuals
8302                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8303                                 for(m=forwardMostMove; m>k; m-=2) {
8304                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8305                                         ourPerpetual = 0; // the current mover did not always check
8306                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8307                                         hisPerpetual = 0; // the opponent did not always check
8308                                 }
8309                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8310                                                                         ourPerpetual, hisPerpetual);
8311                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8312                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8313                                     details = "Xboard adjudication: perpetual checking";
8314                                 } else
8315                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8316                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8317                                 } else
8318                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8319                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8320                                         result = BlackWins;
8321                                         details = "Xboard adjudication: repetition";
8322                                     }
8323                                 } else // it must be XQ
8324                                 // Now check for perpetual chases
8325                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8326                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8327                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8328                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8329                                         static char resdet[MSG_SIZ];
8330                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8331                                         details = resdet;
8332                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8333                                     } else
8334                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8335                                         break; // Abort repetition-checking loop.
8336                                 }
8337                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8338                              }
8339                              if(engineOpponent) {
8340                                SendToProgram("force\n", engineOpponent); // suppress reply
8341                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8342                              }
8343                              GameEnds( result, details, GE_XBOARD );
8344                              return 1;
8345                         }
8346                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8347                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8348                     }
8349                 }
8350
8351                 /* Now we test for 50-move draws. Determine ply count */
8352                 count = forwardMostMove;
8353                 /* look for last irreversble move */
8354                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8355                     count--;
8356                 /* if we hit starting position, add initial plies */
8357                 if( count == backwardMostMove )
8358                     count -= initialRulePlies;
8359                 count = forwardMostMove - count;
8360                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8361                         // adjust reversible move counter for checks in Xiangqi
8362                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8363                         if(i < backwardMostMove) i = backwardMostMove;
8364                         while(i <= forwardMostMove) {
8365                                 lastCheck = inCheck; // check evasion does not count
8366                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8367                                 if(inCheck || lastCheck) count--; // check does not count
8368                                 i++;
8369                         }
8370                 }
8371                 if( count >= 100)
8372                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8373                          /* this is used to judge if draw claims are legal */
8374                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8375                          if(engineOpponent) {
8376                            SendToProgram("force\n", engineOpponent); // suppress reply
8377                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8378                          }
8379                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8380                          return 1;
8381                 }
8382
8383                 /* if draw offer is pending, treat it as a draw claim
8384                  * when draw condition present, to allow engines a way to
8385                  * claim draws before making their move to avoid a race
8386                  * condition occurring after their move
8387                  */
8388                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8389                          char *p = NULL;
8390                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8391                              p = "Draw claim: 50-move rule";
8392                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8393                              p = "Draw claim: 3-fold repetition";
8394                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8395                              p = "Draw claim: insufficient mating material";
8396                          if( p != NULL && canAdjudicate) {
8397                              if(engineOpponent) {
8398                                SendToProgram("force\n", engineOpponent); // suppress reply
8399                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8400                              }
8401                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8402                              return 1;
8403                          }
8404                 }
8405
8406                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8407                     if(engineOpponent) {
8408                       SendToProgram("force\n", engineOpponent); // suppress reply
8409                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8410                     }
8411                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8412                     return 1;
8413                 }
8414         return 0;
8415 }
8416
8417 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8418 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8419 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8420
8421 static int
8422 BitbaseProbe ()
8423 {
8424     int pieces[10], squares[10], cnt=0, r, f, res;
8425     static int loaded;
8426     static PPROBE_EGBB probeBB;
8427     if(!appData.testLegality) return 10;
8428     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8429     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8430     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8431     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8432         ChessSquare piece = boards[forwardMostMove][r][f];
8433         int black = (piece >= BlackPawn);
8434         int type = piece - black*BlackPawn;
8435         if(piece == EmptySquare) continue;
8436         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8437         if(type == WhiteKing) type = WhiteQueen + 1;
8438         type = egbbCode[type];
8439         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8440         pieces[cnt] = type + black*6;
8441         if(++cnt > 5) return 11;
8442     }
8443     pieces[cnt] = squares[cnt] = 0;
8444     // probe EGBB
8445     if(loaded == 2) return 13; // loading failed before
8446     if(loaded == 0) {
8447         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8448         HMODULE lib;
8449         PLOAD_EGBB loadBB;
8450         loaded = 2; // prepare for failure
8451         if(!path) return 13; // no egbb installed
8452         strncpy(buf, path + 8, MSG_SIZ);
8453         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8454         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8455         lib = LoadLibrary(buf);
8456         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8457         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8458         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8459         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8460         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8461         loaded = 1; // success!
8462     }
8463     res = probeBB(forwardMostMove & 1, pieces, squares);
8464     return res > 0 ? 1 : res < 0 ? -1 : 0;
8465 }
8466
8467 char *
8468 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8469 {   // [HGM] book: this routine intercepts moves to simulate book replies
8470     char *bookHit = NULL;
8471
8472     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8473         char buf[MSG_SIZ];
8474         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8475         SendToProgram(buf, cps);
8476     }
8477     //first determine if the incoming move brings opponent into his book
8478     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8479         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8480     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8481     if(bookHit != NULL && !cps->bookSuspend) {
8482         // make sure opponent is not going to reply after receiving move to book position
8483         SendToProgram("force\n", cps);
8484         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8485     }
8486     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8487     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8488     // now arrange restart after book miss
8489     if(bookHit) {
8490         // after a book hit we never send 'go', and the code after the call to this routine
8491         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8492         char buf[MSG_SIZ], *move = bookHit;
8493         if(cps->useSAN) {
8494             int fromX, fromY, toX, toY;
8495             char promoChar;
8496             ChessMove moveType;
8497             move = buf + 30;
8498             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8499                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8500                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8501                                     PosFlags(forwardMostMove),
8502                                     fromY, fromX, toY, toX, promoChar, move);
8503             } else {
8504                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8505                 bookHit = NULL;
8506             }
8507         }
8508         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8509         SendToProgram(buf, cps);
8510         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8511     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8512         SendToProgram("go\n", cps);
8513         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8514     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8515         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8516             SendToProgram("go\n", cps);
8517         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8518     }
8519     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8520 }
8521
8522 int
8523 LoadError (char *errmess, ChessProgramState *cps)
8524 {   // unloads engine and switches back to -ncp mode if it was first
8525     if(cps->initDone) return FALSE;
8526     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8527     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8528     cps->pr = NoProc;
8529     if(cps == &first) {
8530         appData.noChessProgram = TRUE;
8531         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8532         gameMode = BeginningOfGame; ModeHighlight();
8533         SetNCPMode();
8534     }
8535     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8536     DisplayMessage("", ""); // erase waiting message
8537     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8538     return TRUE;
8539 }
8540
8541 char *savedMessage;
8542 ChessProgramState *savedState;
8543 void
8544 DeferredBookMove (void)
8545 {
8546         if(savedState->lastPing != savedState->lastPong)
8547                     ScheduleDelayedEvent(DeferredBookMove, 10);
8548         else
8549         HandleMachineMove(savedMessage, savedState);
8550 }
8551
8552 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8553 static ChessProgramState *stalledEngine;
8554 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8555
8556 void
8557 HandleMachineMove (char *message, ChessProgramState *cps)
8558 {
8559     static char firstLeg[20];
8560     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8561     char realname[MSG_SIZ];
8562     int fromX, fromY, toX, toY;
8563     ChessMove moveType;
8564     char promoChar, roar;
8565     char *p, *pv=buf1;
8566     int machineWhite, oldError;
8567     char *bookHit;
8568
8569     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8570         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8571         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8572             DisplayError(_("Invalid pairing from pairing engine"), 0);
8573             return;
8574         }
8575         pairingReceived = 1;
8576         NextMatchGame();
8577         return; // Skim the pairing messages here.
8578     }
8579
8580     oldError = cps->userError; cps->userError = 0;
8581
8582 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8583     /*
8584      * Kludge to ignore BEL characters
8585      */
8586     while (*message == '\007') message++;
8587
8588     /*
8589      * [HGM] engine debug message: ignore lines starting with '#' character
8590      */
8591     if(cps->debug && *message == '#') return;
8592
8593     /*
8594      * Look for book output
8595      */
8596     if (cps == &first && bookRequested) {
8597         if (message[0] == '\t' || message[0] == ' ') {
8598             /* Part of the book output is here; append it */
8599             strcat(bookOutput, message);
8600             strcat(bookOutput, "  \n");
8601             return;
8602         } else if (bookOutput[0] != NULLCHAR) {
8603             /* All of book output has arrived; display it */
8604             char *p = bookOutput;
8605             while (*p != NULLCHAR) {
8606                 if (*p == '\t') *p = ' ';
8607                 p++;
8608             }
8609             DisplayInformation(bookOutput);
8610             bookRequested = FALSE;
8611             /* Fall through to parse the current output */
8612         }
8613     }
8614
8615     /*
8616      * Look for machine move.
8617      */
8618     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8619         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8620     {
8621         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8622             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8623             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8624             stalledEngine = cps;
8625             if(appData.ponderNextMove) { // bring opponent out of ponder
8626                 if(gameMode == TwoMachinesPlay) {
8627                     if(cps->other->pause)
8628                         PauseEngine(cps->other);
8629                     else
8630                         SendToProgram("easy\n", cps->other);
8631                 }
8632             }
8633             StopClocks();
8634             return;
8635         }
8636
8637       if(cps->usePing) {
8638
8639         /* This method is only useful on engines that support ping */
8640         if(abortEngineThink) {
8641             if (appData.debugMode) {
8642                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8643             }
8644             SendToProgram("undo\n", cps);
8645             return;
8646         }
8647
8648         if (cps->lastPing != cps->lastPong) {
8649             /* Extra move from before last new; ignore */
8650             if (appData.debugMode) {
8651                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8652             }
8653           return;
8654         }
8655
8656       } else {
8657
8658         switch (gameMode) {
8659           case BeginningOfGame:
8660             /* Extra move from before last reset; ignore */
8661             if (appData.debugMode) {
8662                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8663             }
8664             return;
8665
8666           case EndOfGame:
8667           case IcsIdle:
8668           default:
8669             /* Extra move after we tried to stop.  The mode test is
8670                not a reliable way of detecting this problem, but it's
8671                the best we can do on engines that don't support ping.
8672             */
8673             if (appData.debugMode) {
8674                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8675                         cps->which, gameMode);
8676             }
8677             SendToProgram("undo\n", cps);
8678             return;
8679
8680           case MachinePlaysWhite:
8681           case IcsPlayingWhite:
8682             machineWhite = TRUE;
8683             break;
8684
8685           case MachinePlaysBlack:
8686           case IcsPlayingBlack:
8687             machineWhite = FALSE;
8688             break;
8689
8690           case TwoMachinesPlay:
8691             machineWhite = (cps->twoMachinesColor[0] == 'w');
8692             break;
8693         }
8694         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8695             if (appData.debugMode) {
8696                 fprintf(debugFP,
8697                         "Ignoring move out of turn by %s, gameMode %d"
8698                         ", forwardMost %d\n",
8699                         cps->which, gameMode, forwardMostMove);
8700             }
8701             return;
8702         }
8703       }
8704
8705         if(cps->alphaRank) AlphaRank(machineMove, 4);
8706
8707         // [HGM] lion: (some very limited) support for Alien protocol
8708         killX = killY = kill2X = kill2Y = -1;
8709         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8710             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8711             return;
8712         }
8713         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8714             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8715             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8716         }
8717         if(firstLeg[0]) { // there was a previous leg;
8718             // only support case where same piece makes two step
8719             char buf[20], *p = machineMove+1, *q = buf+1, f;
8720             safeStrCpy(buf, machineMove, 20);
8721             while(isdigit(*q)) q++; // find start of to-square
8722             safeStrCpy(machineMove, firstLeg, 20);
8723             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8724             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8725             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8726             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8727             firstLeg[0] = NULLCHAR;
8728         }
8729
8730         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8731                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8732             /* Machine move could not be parsed; ignore it. */
8733           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8734                     machineMove, _(cps->which));
8735             DisplayMoveError(buf1);
8736             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8737                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8738             if (gameMode == TwoMachinesPlay) {
8739               GameEnds(machineWhite ? BlackWins : WhiteWins,
8740                        buf1, GE_XBOARD);
8741             }
8742             return;
8743         }
8744
8745         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8746         /* So we have to redo legality test with true e.p. status here,  */
8747         /* to make sure an illegal e.p. capture does not slip through,   */
8748         /* to cause a forfeit on a justified illegal-move complaint      */
8749         /* of the opponent.                                              */
8750         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8751            ChessMove moveType;
8752            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8753                              fromY, fromX, toY, toX, promoChar);
8754             if(moveType == IllegalMove) {
8755               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8756                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8757                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8758                            buf1, GE_XBOARD);
8759                 return;
8760            } else if(!appData.fischerCastling)
8761            /* [HGM] Kludge to handle engines that send FRC-style castling
8762               when they shouldn't (like TSCP-Gothic) */
8763            switch(moveType) {
8764              case WhiteASideCastleFR:
8765              case BlackASideCastleFR:
8766                toX+=2;
8767                currentMoveString[2]++;
8768                break;
8769              case WhiteHSideCastleFR:
8770              case BlackHSideCastleFR:
8771                toX--;
8772                currentMoveString[2]--;
8773                break;
8774              default: ; // nothing to do, but suppresses warning of pedantic compilers
8775            }
8776         }
8777         hintRequested = FALSE;
8778         lastHint[0] = NULLCHAR;
8779         bookRequested = FALSE;
8780         /* Program may be pondering now */
8781         cps->maybeThinking = TRUE;
8782         if (cps->sendTime == 2) cps->sendTime = 1;
8783         if (cps->offeredDraw) cps->offeredDraw--;
8784
8785         /* [AS] Save move info*/
8786         pvInfoList[ forwardMostMove ].score = programStats.score;
8787         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8788         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8789
8790         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8791
8792         /* Test suites abort the 'game' after one move */
8793         if(*appData.finger) {
8794            static FILE *f;
8795            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8796            if(!f) f = fopen(appData.finger, "w");
8797            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8798            else { DisplayFatalError("Bad output file", errno, 0); return; }
8799            free(fen);
8800            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8801         }
8802         if(appData.epd) {
8803            if(solvingTime >= 0) {
8804               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8805               totalTime += solvingTime; first.matchWins++;
8806            } else {
8807               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8808               second.matchWins++;
8809            }
8810            OutputKibitz(2, buf1);
8811            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8812         }
8813
8814         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8815         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8816             int count = 0;
8817
8818             while( count < adjudicateLossPlies ) {
8819                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8820
8821                 if( count & 1 ) {
8822                     score = -score; /* Flip score for winning side */
8823                 }
8824
8825                 if( score > appData.adjudicateLossThreshold ) {
8826                     break;
8827                 }
8828
8829                 count++;
8830             }
8831
8832             if( count >= adjudicateLossPlies ) {
8833                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8834
8835                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8836                     "Xboard adjudication",
8837                     GE_XBOARD );
8838
8839                 return;
8840             }
8841         }
8842
8843         if(Adjudicate(cps)) {
8844             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8845             return; // [HGM] adjudicate: for all automatic game ends
8846         }
8847
8848 #if ZIPPY
8849         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8850             first.initDone) {
8851           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8852                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8853                 SendToICS("draw ");
8854                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8855           }
8856           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8857           ics_user_moved = 1;
8858           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8859                 char buf[3*MSG_SIZ];
8860
8861                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8862                         programStats.score / 100.,
8863                         programStats.depth,
8864                         programStats.time / 100.,
8865                         (unsigned int)programStats.nodes,
8866                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8867                         programStats.movelist);
8868                 SendToICS(buf);
8869           }
8870         }
8871 #endif
8872
8873         /* [AS] Clear stats for next move */
8874         ClearProgramStats();
8875         thinkOutput[0] = NULLCHAR;
8876         hiddenThinkOutputState = 0;
8877
8878         bookHit = NULL;
8879         if (gameMode == TwoMachinesPlay) {
8880             /* [HGM] relaying draw offers moved to after reception of move */
8881             /* and interpreting offer as claim if it brings draw condition */
8882             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8883                 SendToProgram("draw\n", cps->other);
8884             }
8885             if (cps->other->sendTime) {
8886                 SendTimeRemaining(cps->other,
8887                                   cps->other->twoMachinesColor[0] == 'w');
8888             }
8889             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8890             if (firstMove && !bookHit) {
8891                 firstMove = FALSE;
8892                 if (cps->other->useColors) {
8893                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8894                 }
8895                 SendToProgram("go\n", cps->other);
8896             }
8897             cps->other->maybeThinking = TRUE;
8898         }
8899
8900         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8901
8902         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8903
8904         if (!pausing && appData.ringBellAfterMoves) {
8905             if(!roar) RingBell();
8906         }
8907
8908         /*
8909          * Reenable menu items that were disabled while
8910          * machine was thinking
8911          */
8912         if (gameMode != TwoMachinesPlay)
8913             SetUserThinkingEnables();
8914
8915         // [HGM] book: after book hit opponent has received move and is now in force mode
8916         // force the book reply into it, and then fake that it outputted this move by jumping
8917         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8918         if(bookHit) {
8919                 static char bookMove[MSG_SIZ]; // a bit generous?
8920
8921                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8922                 strcat(bookMove, bookHit);
8923                 message = bookMove;
8924                 cps = cps->other;
8925                 programStats.nodes = programStats.depth = programStats.time =
8926                 programStats.score = programStats.got_only_move = 0;
8927                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8928
8929                 if(cps->lastPing != cps->lastPong) {
8930                     savedMessage = message; // args for deferred call
8931                     savedState = cps;
8932                     ScheduleDelayedEvent(DeferredBookMove, 10);
8933                     return;
8934                 }
8935                 goto FakeBookMove;
8936         }
8937
8938         return;
8939     }
8940
8941     /* Set special modes for chess engines.  Later something general
8942      *  could be added here; for now there is just one kludge feature,
8943      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8944      *  when "xboard" is given as an interactive command.
8945      */
8946     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8947         cps->useSigint = FALSE;
8948         cps->useSigterm = FALSE;
8949     }
8950     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8951       ParseFeatures(message+8, cps);
8952       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8953     }
8954
8955     if (!strncmp(message, "setup ", 6) && 
8956         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8957           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8958                                         ) { // [HGM] allow first engine to define opening position
8959       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8960       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8961       *buf = NULLCHAR;
8962       if(sscanf(message, "setup (%s", buf) == 1) {
8963         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
8964         ASSIGN(appData.pieceToCharTable, buf);
8965       }
8966       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8967       if(dummy >= 3) {
8968         while(message[s] && message[s++] != ' ');
8969         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8970            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8971             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8972             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8973           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8974           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
8975           startedFromSetupPosition = FALSE;
8976         }
8977       }
8978       if(startedFromSetupPosition) return;
8979       ParseFEN(boards[0], &dummy, message+s, FALSE);
8980       DrawPosition(TRUE, boards[0]);
8981       CopyBoard(initialPosition, boards[0]);
8982       startedFromSetupPosition = TRUE;
8983       return;
8984     }
8985     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
8986       ChessSquare piece = WhitePawn;
8987       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
8988       if(*p == '+') piece = CHUPROMOTED WhitePawn, ID = *++p;
8989       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
8990       piece += CharToPiece(ID & 255) - WhitePawn;
8991       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
8992       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
8993       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
8994       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
8995       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
8996       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
8997                                                && gameInfo.variant != VariantGreat
8998                                                && gameInfo.variant != VariantFairy    ) return;
8999       if(piece < EmptySquare) {
9000         pieceDefs = TRUE;
9001         ASSIGN(pieceDesc[piece], buf1);
9002         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9003       }
9004       return;
9005     }
9006     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9007      * want this, I was asked to put it in, and obliged.
9008      */
9009     if (!strncmp(message, "setboard ", 9)) {
9010         Board initial_position;
9011
9012         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9013
9014         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9015             DisplayError(_("Bad FEN received from engine"), 0);
9016             return ;
9017         } else {
9018            Reset(TRUE, FALSE);
9019            CopyBoard(boards[0], initial_position);
9020            initialRulePlies = FENrulePlies;
9021            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9022            else gameMode = MachinePlaysBlack;
9023            DrawPosition(FALSE, boards[currentMove]);
9024         }
9025         return;
9026     }
9027
9028     /*
9029      * Look for communication commands
9030      */
9031     if (!strncmp(message, "telluser ", 9)) {
9032         if(message[9] == '\\' && message[10] == '\\')
9033             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9034         PlayTellSound();
9035         DisplayNote(message + 9);
9036         return;
9037     }
9038     if (!strncmp(message, "tellusererror ", 14)) {
9039         cps->userError = 1;
9040         if(message[14] == '\\' && message[15] == '\\')
9041             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9042         PlayTellSound();
9043         DisplayError(message + 14, 0);
9044         return;
9045     }
9046     if (!strncmp(message, "tellopponent ", 13)) {
9047       if (appData.icsActive) {
9048         if (loggedOn) {
9049           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9050           SendToICS(buf1);
9051         }
9052       } else {
9053         DisplayNote(message + 13);
9054       }
9055       return;
9056     }
9057     if (!strncmp(message, "tellothers ", 11)) {
9058       if (appData.icsActive) {
9059         if (loggedOn) {
9060           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9061           SendToICS(buf1);
9062         }
9063       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9064       return;
9065     }
9066     if (!strncmp(message, "tellall ", 8)) {
9067       if (appData.icsActive) {
9068         if (loggedOn) {
9069           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9070           SendToICS(buf1);
9071         }
9072       } else {
9073         DisplayNote(message + 8);
9074       }
9075       return;
9076     }
9077     if (strncmp(message, "warning", 7) == 0) {
9078         /* Undocumented feature, use tellusererror in new code */
9079         DisplayError(message, 0);
9080         return;
9081     }
9082     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9083         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9084         strcat(realname, " query");
9085         AskQuestion(realname, buf2, buf1, cps->pr);
9086         return;
9087     }
9088     /* Commands from the engine directly to ICS.  We don't allow these to be
9089      *  sent until we are logged on. Crafty kibitzes have been known to
9090      *  interfere with the login process.
9091      */
9092     if (loggedOn) {
9093         if (!strncmp(message, "tellics ", 8)) {
9094             SendToICS(message + 8);
9095             SendToICS("\n");
9096             return;
9097         }
9098         if (!strncmp(message, "tellicsnoalias ", 15)) {
9099             SendToICS(ics_prefix);
9100             SendToICS(message + 15);
9101             SendToICS("\n");
9102             return;
9103         }
9104         /* The following are for backward compatibility only */
9105         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9106             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9107             SendToICS(ics_prefix);
9108             SendToICS(message);
9109             SendToICS("\n");
9110             return;
9111         }
9112     }
9113     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9114         if(initPing == cps->lastPong) {
9115             if(gameInfo.variant == VariantUnknown) {
9116                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9117                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9118                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9119             }
9120             initPing = -1;
9121         }
9122         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9123             abortEngineThink = FALSE;
9124             DisplayMessage("", "");
9125             ThawUI();
9126         }
9127         return;
9128     }
9129     if(!strncmp(message, "highlight ", 10)) {
9130         if(appData.testLegality && !*engineVariant && appData.markers) return;
9131         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9132         return;
9133     }
9134     if(!strncmp(message, "click ", 6)) {
9135         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9136         if(appData.testLegality || !appData.oneClick) return;
9137         sscanf(message+6, "%c%d%c", &f, &y, &c);
9138         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9139         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9140         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9141         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9142         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9143         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9144             LeftClick(Release, lastLeftX, lastLeftY);
9145         controlKey  = (c == ',');
9146         LeftClick(Press, x, y);
9147         LeftClick(Release, x, y);
9148         first.highlight = f;
9149         return;
9150     }
9151     /*
9152      * If the move is illegal, cancel it and redraw the board.
9153      * Also deal with other error cases.  Matching is rather loose
9154      * here to accommodate engines written before the spec.
9155      */
9156     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9157         strncmp(message, "Error", 5) == 0) {
9158         if (StrStr(message, "name") ||
9159             StrStr(message, "rating") || StrStr(message, "?") ||
9160             StrStr(message, "result") || StrStr(message, "board") ||
9161             StrStr(message, "bk") || StrStr(message, "computer") ||
9162             StrStr(message, "variant") || StrStr(message, "hint") ||
9163             StrStr(message, "random") || StrStr(message, "depth") ||
9164             StrStr(message, "accepted")) {
9165             return;
9166         }
9167         if (StrStr(message, "protover")) {
9168           /* Program is responding to input, so it's apparently done
9169              initializing, and this error message indicates it is
9170              protocol version 1.  So we don't need to wait any longer
9171              for it to initialize and send feature commands. */
9172           FeatureDone(cps, 1);
9173           cps->protocolVersion = 1;
9174           return;
9175         }
9176         cps->maybeThinking = FALSE;
9177
9178         if (StrStr(message, "draw")) {
9179             /* Program doesn't have "draw" command */
9180             cps->sendDrawOffers = 0;
9181             return;
9182         }
9183         if (cps->sendTime != 1 &&
9184             (StrStr(message, "time") || StrStr(message, "otim"))) {
9185           /* Program apparently doesn't have "time" or "otim" command */
9186           cps->sendTime = 0;
9187           return;
9188         }
9189         if (StrStr(message, "analyze")) {
9190             cps->analysisSupport = FALSE;
9191             cps->analyzing = FALSE;
9192 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9193             EditGameEvent(); // [HGM] try to preserve loaded game
9194             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9195             DisplayError(buf2, 0);
9196             return;
9197         }
9198         if (StrStr(message, "(no matching move)st")) {
9199           /* Special kludge for GNU Chess 4 only */
9200           cps->stKludge = TRUE;
9201           SendTimeControl(cps, movesPerSession, timeControl,
9202                           timeIncrement, appData.searchDepth,
9203                           searchTime);
9204           return;
9205         }
9206         if (StrStr(message, "(no matching move)sd")) {
9207           /* Special kludge for GNU Chess 4 only */
9208           cps->sdKludge = TRUE;
9209           SendTimeControl(cps, movesPerSession, timeControl,
9210                           timeIncrement, appData.searchDepth,
9211                           searchTime);
9212           return;
9213         }
9214         if (!StrStr(message, "llegal")) {
9215             return;
9216         }
9217         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9218             gameMode == IcsIdle) return;
9219         if (forwardMostMove <= backwardMostMove) return;
9220         if (pausing) PauseEvent();
9221       if(appData.forceIllegal) {
9222             // [HGM] illegal: machine refused move; force position after move into it
9223           SendToProgram("force\n", cps);
9224           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9225                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9226                 // when black is to move, while there might be nothing on a2 or black
9227                 // might already have the move. So send the board as if white has the move.
9228                 // But first we must change the stm of the engine, as it refused the last move
9229                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9230                 if(WhiteOnMove(forwardMostMove)) {
9231                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9232                     SendBoard(cps, forwardMostMove); // kludgeless board
9233                 } else {
9234                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9235                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9236                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9237                 }
9238           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9239             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9240                  gameMode == TwoMachinesPlay)
9241               SendToProgram("go\n", cps);
9242             return;
9243       } else
9244         if (gameMode == PlayFromGameFile) {
9245             /* Stop reading this game file */
9246             gameMode = EditGame;
9247             ModeHighlight();
9248         }
9249         /* [HGM] illegal-move claim should forfeit game when Xboard */
9250         /* only passes fully legal moves                            */
9251         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9252             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9253                                 "False illegal-move claim", GE_XBOARD );
9254             return; // do not take back move we tested as valid
9255         }
9256         currentMove = forwardMostMove-1;
9257         DisplayMove(currentMove-1); /* before DisplayMoveError */
9258         SwitchClocks(forwardMostMove-1); // [HGM] race
9259         DisplayBothClocks();
9260         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9261                 parseList[currentMove], _(cps->which));
9262         DisplayMoveError(buf1);
9263         DrawPosition(FALSE, boards[currentMove]);
9264
9265         SetUserThinkingEnables();
9266         return;
9267     }
9268     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9269         /* Program has a broken "time" command that
9270            outputs a string not ending in newline.
9271            Don't use it. */
9272         cps->sendTime = 0;
9273     }
9274     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9275         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9276             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9277     }
9278
9279     /*
9280      * If chess program startup fails, exit with an error message.
9281      * Attempts to recover here are futile. [HGM] Well, we try anyway
9282      */
9283     if ((StrStr(message, "unknown host") != NULL)
9284         || (StrStr(message, "No remote directory") != NULL)
9285         || (StrStr(message, "not found") != NULL)
9286         || (StrStr(message, "No such file") != NULL)
9287         || (StrStr(message, "can't alloc") != NULL)
9288         || (StrStr(message, "Permission denied") != NULL)) {
9289
9290         cps->maybeThinking = FALSE;
9291         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9292                 _(cps->which), cps->program, cps->host, message);
9293         RemoveInputSource(cps->isr);
9294         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9295             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9296             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9297         }
9298         return;
9299     }
9300
9301     /*
9302      * Look for hint output
9303      */
9304     if (sscanf(message, "Hint: %s", buf1) == 1) {
9305         if (cps == &first && hintRequested) {
9306             hintRequested = FALSE;
9307             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9308                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9309                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9310                                     PosFlags(forwardMostMove),
9311                                     fromY, fromX, toY, toX, promoChar, buf1);
9312                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9313                 DisplayInformation(buf2);
9314             } else {
9315                 /* Hint move could not be parsed!? */
9316               snprintf(buf2, sizeof(buf2),
9317                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9318                         buf1, _(cps->which));
9319                 DisplayError(buf2, 0);
9320             }
9321         } else {
9322           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9323         }
9324         return;
9325     }
9326
9327     /*
9328      * Ignore other messages if game is not in progress
9329      */
9330     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9331         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9332
9333     /*
9334      * look for win, lose, draw, or draw offer
9335      */
9336     if (strncmp(message, "1-0", 3) == 0) {
9337         char *p, *q, *r = "";
9338         p = strchr(message, '{');
9339         if (p) {
9340             q = strchr(p, '}');
9341             if (q) {
9342                 *q = NULLCHAR;
9343                 r = p + 1;
9344             }
9345         }
9346         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9347         return;
9348     } else if (strncmp(message, "0-1", 3) == 0) {
9349         char *p, *q, *r = "";
9350         p = strchr(message, '{');
9351         if (p) {
9352             q = strchr(p, '}');
9353             if (q) {
9354                 *q = NULLCHAR;
9355                 r = p + 1;
9356             }
9357         }
9358         /* Kludge for Arasan 4.1 bug */
9359         if (strcmp(r, "Black resigns") == 0) {
9360             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9361             return;
9362         }
9363         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9364         return;
9365     } else if (strncmp(message, "1/2", 3) == 0) {
9366         char *p, *q, *r = "";
9367         p = strchr(message, '{');
9368         if (p) {
9369             q = strchr(p, '}');
9370             if (q) {
9371                 *q = NULLCHAR;
9372                 r = p + 1;
9373             }
9374         }
9375
9376         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9377         return;
9378
9379     } else if (strncmp(message, "White resign", 12) == 0) {
9380         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9381         return;
9382     } else if (strncmp(message, "Black resign", 12) == 0) {
9383         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9384         return;
9385     } else if (strncmp(message, "White matches", 13) == 0 ||
9386                strncmp(message, "Black matches", 13) == 0   ) {
9387         /* [HGM] ignore GNUShogi noises */
9388         return;
9389     } else if (strncmp(message, "White", 5) == 0 &&
9390                message[5] != '(' &&
9391                StrStr(message, "Black") == NULL) {
9392         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9393         return;
9394     } else if (strncmp(message, "Black", 5) == 0 &&
9395                message[5] != '(') {
9396         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9397         return;
9398     } else if (strcmp(message, "resign") == 0 ||
9399                strcmp(message, "computer resigns") == 0) {
9400         switch (gameMode) {
9401           case MachinePlaysBlack:
9402           case IcsPlayingBlack:
9403             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9404             break;
9405           case MachinePlaysWhite:
9406           case IcsPlayingWhite:
9407             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9408             break;
9409           case TwoMachinesPlay:
9410             if (cps->twoMachinesColor[0] == 'w')
9411               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9412             else
9413               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9414             break;
9415           default:
9416             /* can't happen */
9417             break;
9418         }
9419         return;
9420     } else if (strncmp(message, "opponent mates", 14) == 0) {
9421         switch (gameMode) {
9422           case MachinePlaysBlack:
9423           case IcsPlayingBlack:
9424             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9425             break;
9426           case MachinePlaysWhite:
9427           case IcsPlayingWhite:
9428             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9429             break;
9430           case TwoMachinesPlay:
9431             if (cps->twoMachinesColor[0] == 'w')
9432               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9433             else
9434               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9435             break;
9436           default:
9437             /* can't happen */
9438             break;
9439         }
9440         return;
9441     } else if (strncmp(message, "computer mates", 14) == 0) {
9442         switch (gameMode) {
9443           case MachinePlaysBlack:
9444           case IcsPlayingBlack:
9445             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9446             break;
9447           case MachinePlaysWhite:
9448           case IcsPlayingWhite:
9449             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9450             break;
9451           case TwoMachinesPlay:
9452             if (cps->twoMachinesColor[0] == 'w')
9453               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9454             else
9455               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9456             break;
9457           default:
9458             /* can't happen */
9459             break;
9460         }
9461         return;
9462     } else if (strncmp(message, "checkmate", 9) == 0) {
9463         if (WhiteOnMove(forwardMostMove)) {
9464             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9465         } else {
9466             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9467         }
9468         return;
9469     } else if (strstr(message, "Draw") != NULL ||
9470                strstr(message, "game is a draw") != NULL) {
9471         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9472         return;
9473     } else if (strstr(message, "offer") != NULL &&
9474                strstr(message, "draw") != NULL) {
9475 #if ZIPPY
9476         if (appData.zippyPlay && first.initDone) {
9477             /* Relay offer to ICS */
9478             SendToICS(ics_prefix);
9479             SendToICS("draw\n");
9480         }
9481 #endif
9482         cps->offeredDraw = 2; /* valid until this engine moves twice */
9483         if (gameMode == TwoMachinesPlay) {
9484             if (cps->other->offeredDraw) {
9485                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9486             /* [HGM] in two-machine mode we delay relaying draw offer      */
9487             /* until after we also have move, to see if it is really claim */
9488             }
9489         } else if (gameMode == MachinePlaysWhite ||
9490                    gameMode == MachinePlaysBlack) {
9491           if (userOfferedDraw) {
9492             DisplayInformation(_("Machine accepts your draw offer"));
9493             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9494           } else {
9495             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9496           }
9497         }
9498     }
9499
9500
9501     /*
9502      * Look for thinking output
9503      */
9504     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9505           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9506                                 ) {
9507         int plylev, mvleft, mvtot, curscore, time;
9508         char mvname[MOVE_LEN];
9509         u64 nodes; // [DM]
9510         char plyext;
9511         int ignore = FALSE;
9512         int prefixHint = FALSE;
9513         mvname[0] = NULLCHAR;
9514
9515         switch (gameMode) {
9516           case MachinePlaysBlack:
9517           case IcsPlayingBlack:
9518             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9519             break;
9520           case MachinePlaysWhite:
9521           case IcsPlayingWhite:
9522             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9523             break;
9524           case AnalyzeMode:
9525           case AnalyzeFile:
9526             break;
9527           case IcsObserving: /* [DM] icsEngineAnalyze */
9528             if (!appData.icsEngineAnalyze) ignore = TRUE;
9529             break;
9530           case TwoMachinesPlay:
9531             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9532                 ignore = TRUE;
9533             }
9534             break;
9535           default:
9536             ignore = TRUE;
9537             break;
9538         }
9539
9540         if (!ignore) {
9541             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9542             buf1[0] = NULLCHAR;
9543             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9544                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9545                 char score_buf[MSG_SIZ];
9546
9547                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9548                     nodes += u64Const(0x100000000);
9549
9550                 if (plyext != ' ' && plyext != '\t') {
9551                     time *= 100;
9552                 }
9553
9554                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9555                 if( cps->scoreIsAbsolute &&
9556                     ( gameMode == MachinePlaysBlack ||
9557                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9558                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9559                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9560                      !WhiteOnMove(currentMove)
9561                     ) )
9562                 {
9563                     curscore = -curscore;
9564                 }
9565
9566                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9567
9568                 if(*bestMove) { // rememer time best EPD move was first found
9569                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9570                     ChessMove mt;
9571                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9572                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9573                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9574                 }
9575
9576                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9577                         char buf[MSG_SIZ];
9578                         FILE *f;
9579                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9580                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9581                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9582                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9583                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9584                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9585                                 fclose(f);
9586                         }
9587                         else
9588                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9589                           DisplayError(_("failed writing PV"), 0);
9590                 }
9591
9592                 tempStats.depth = plylev;
9593                 tempStats.nodes = nodes;
9594                 tempStats.time = time;
9595                 tempStats.score = curscore;
9596                 tempStats.got_only_move = 0;
9597
9598                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9599                         int ticklen;
9600
9601                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9602                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9603                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9604                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9605                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9606                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9607                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9608                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9609                 }
9610
9611                 /* Buffer overflow protection */
9612                 if (pv[0] != NULLCHAR) {
9613                     if (strlen(pv) >= sizeof(tempStats.movelist)
9614                         && appData.debugMode) {
9615                         fprintf(debugFP,
9616                                 "PV is too long; using the first %u bytes.\n",
9617                                 (unsigned) sizeof(tempStats.movelist) - 1);
9618                     }
9619
9620                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9621                 } else {
9622                     sprintf(tempStats.movelist, " no PV\n");
9623                 }
9624
9625                 if (tempStats.seen_stat) {
9626                     tempStats.ok_to_send = 1;
9627                 }
9628
9629                 if (strchr(tempStats.movelist, '(') != NULL) {
9630                     tempStats.line_is_book = 1;
9631                     tempStats.nr_moves = 0;
9632                     tempStats.moves_left = 0;
9633                 } else {
9634                     tempStats.line_is_book = 0;
9635                 }
9636
9637                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9638                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9639
9640                 SendProgramStatsToFrontend( cps, &tempStats );
9641
9642                 /*
9643                     [AS] Protect the thinkOutput buffer from overflow... this
9644                     is only useful if buf1 hasn't overflowed first!
9645                 */
9646                 if(curscore >= MATE_SCORE) 
9647                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9648                 else if(curscore <= -MATE_SCORE) 
9649                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9650                 else
9651                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9652                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9653                          plylev,
9654                          (gameMode == TwoMachinesPlay ?
9655                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9656                          score_buf,
9657                          prefixHint ? lastHint : "",
9658                          prefixHint ? " " : "" );
9659
9660                 if( buf1[0] != NULLCHAR ) {
9661                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9662
9663                     if( strlen(pv) > max_len ) {
9664                         if( appData.debugMode) {
9665                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9666                         }
9667                         pv[max_len+1] = '\0';
9668                     }
9669
9670                     strcat( thinkOutput, pv);
9671                 }
9672
9673                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9674                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9675                     DisplayMove(currentMove - 1);
9676                 }
9677                 return;
9678
9679             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9680                 /* crafty (9.25+) says "(only move) <move>"
9681                  * if there is only 1 legal move
9682                  */
9683                 sscanf(p, "(only move) %s", buf1);
9684                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9685                 sprintf(programStats.movelist, "%s (only move)", buf1);
9686                 programStats.depth = 1;
9687                 programStats.nr_moves = 1;
9688                 programStats.moves_left = 1;
9689                 programStats.nodes = 1;
9690                 programStats.time = 1;
9691                 programStats.got_only_move = 1;
9692
9693                 /* Not really, but we also use this member to
9694                    mean "line isn't going to change" (Crafty
9695                    isn't searching, so stats won't change) */
9696                 programStats.line_is_book = 1;
9697
9698                 SendProgramStatsToFrontend( cps, &programStats );
9699
9700                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9701                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9702                     DisplayMove(currentMove - 1);
9703                 }
9704                 return;
9705             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9706                               &time, &nodes, &plylev, &mvleft,
9707                               &mvtot, mvname) >= 5) {
9708                 /* The stat01: line is from Crafty (9.29+) in response
9709                    to the "." command */
9710                 programStats.seen_stat = 1;
9711                 cps->maybeThinking = TRUE;
9712
9713                 if (programStats.got_only_move || !appData.periodicUpdates)
9714                   return;
9715
9716                 programStats.depth = plylev;
9717                 programStats.time = time;
9718                 programStats.nodes = nodes;
9719                 programStats.moves_left = mvleft;
9720                 programStats.nr_moves = mvtot;
9721                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9722                 programStats.ok_to_send = 1;
9723                 programStats.movelist[0] = '\0';
9724
9725                 SendProgramStatsToFrontend( cps, &programStats );
9726
9727                 return;
9728
9729             } else if (strncmp(message,"++",2) == 0) {
9730                 /* Crafty 9.29+ outputs this */
9731                 programStats.got_fail = 2;
9732                 return;
9733
9734             } else if (strncmp(message,"--",2) == 0) {
9735                 /* Crafty 9.29+ outputs this */
9736                 programStats.got_fail = 1;
9737                 return;
9738
9739             } else if (thinkOutput[0] != NULLCHAR &&
9740                        strncmp(message, "    ", 4) == 0) {
9741                 unsigned message_len;
9742
9743                 p = message;
9744                 while (*p && *p == ' ') p++;
9745
9746                 message_len = strlen( p );
9747
9748                 /* [AS] Avoid buffer overflow */
9749                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9750                     strcat(thinkOutput, " ");
9751                     strcat(thinkOutput, p);
9752                 }
9753
9754                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9755                     strcat(programStats.movelist, " ");
9756                     strcat(programStats.movelist, p);
9757                 }
9758
9759                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9760                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9761                     DisplayMove(currentMove - 1);
9762                 }
9763                 return;
9764             }
9765         }
9766         else {
9767             buf1[0] = NULLCHAR;
9768
9769             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9770                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9771             {
9772                 ChessProgramStats cpstats;
9773
9774                 if (plyext != ' ' && plyext != '\t') {
9775                     time *= 100;
9776                 }
9777
9778                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9779                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9780                     curscore = -curscore;
9781                 }
9782
9783                 cpstats.depth = plylev;
9784                 cpstats.nodes = nodes;
9785                 cpstats.time = time;
9786                 cpstats.score = curscore;
9787                 cpstats.got_only_move = 0;
9788                 cpstats.movelist[0] = '\0';
9789
9790                 if (buf1[0] != NULLCHAR) {
9791                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9792                 }
9793
9794                 cpstats.ok_to_send = 0;
9795                 cpstats.line_is_book = 0;
9796                 cpstats.nr_moves = 0;
9797                 cpstats.moves_left = 0;
9798
9799                 SendProgramStatsToFrontend( cps, &cpstats );
9800             }
9801         }
9802     }
9803 }
9804
9805
9806 /* Parse a game score from the character string "game", and
9807    record it as the history of the current game.  The game
9808    score is NOT assumed to start from the standard position.
9809    The display is not updated in any way.
9810    */
9811 void
9812 ParseGameHistory (char *game)
9813 {
9814     ChessMove moveType;
9815     int fromX, fromY, toX, toY, boardIndex;
9816     char promoChar;
9817     char *p, *q;
9818     char buf[MSG_SIZ];
9819
9820     if (appData.debugMode)
9821       fprintf(debugFP, "Parsing game history: %s\n", game);
9822
9823     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9824     gameInfo.site = StrSave(appData.icsHost);
9825     gameInfo.date = PGNDate();
9826     gameInfo.round = StrSave("-");
9827
9828     /* Parse out names of players */
9829     while (*game == ' ') game++;
9830     p = buf;
9831     while (*game != ' ') *p++ = *game++;
9832     *p = NULLCHAR;
9833     gameInfo.white = StrSave(buf);
9834     while (*game == ' ') game++;
9835     p = buf;
9836     while (*game != ' ' && *game != '\n') *p++ = *game++;
9837     *p = NULLCHAR;
9838     gameInfo.black = StrSave(buf);
9839
9840     /* Parse moves */
9841     boardIndex = blackPlaysFirst ? 1 : 0;
9842     yynewstr(game);
9843     for (;;) {
9844         yyboardindex = boardIndex;
9845         moveType = (ChessMove) Myylex();
9846         switch (moveType) {
9847           case IllegalMove:             /* maybe suicide chess, etc. */
9848   if (appData.debugMode) {
9849     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9850     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9851     setbuf(debugFP, NULL);
9852   }
9853           case WhitePromotion:
9854           case BlackPromotion:
9855           case WhiteNonPromotion:
9856           case BlackNonPromotion:
9857           case NormalMove:
9858           case FirstLeg:
9859           case WhiteCapturesEnPassant:
9860           case BlackCapturesEnPassant:
9861           case WhiteKingSideCastle:
9862           case WhiteQueenSideCastle:
9863           case BlackKingSideCastle:
9864           case BlackQueenSideCastle:
9865           case WhiteKingSideCastleWild:
9866           case WhiteQueenSideCastleWild:
9867           case BlackKingSideCastleWild:
9868           case BlackQueenSideCastleWild:
9869           /* PUSH Fabien */
9870           case WhiteHSideCastleFR:
9871           case WhiteASideCastleFR:
9872           case BlackHSideCastleFR:
9873           case BlackASideCastleFR:
9874           /* POP Fabien */
9875             fromX = currentMoveString[0] - AAA;
9876             fromY = currentMoveString[1] - ONE;
9877             toX = currentMoveString[2] - AAA;
9878             toY = currentMoveString[3] - ONE;
9879             promoChar = currentMoveString[4];
9880             break;
9881           case WhiteDrop:
9882           case BlackDrop:
9883             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9884             fromX = moveType == WhiteDrop ?
9885               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9886             (int) CharToPiece(ToLower(currentMoveString[0]));
9887             fromY = DROP_RANK;
9888             toX = currentMoveString[2] - AAA;
9889             toY = currentMoveString[3] - ONE;
9890             promoChar = NULLCHAR;
9891             break;
9892           case AmbiguousMove:
9893             /* bug? */
9894             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9895   if (appData.debugMode) {
9896     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9897     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9898     setbuf(debugFP, NULL);
9899   }
9900             DisplayError(buf, 0);
9901             return;
9902           case ImpossibleMove:
9903             /* bug? */
9904             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9905   if (appData.debugMode) {
9906     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9907     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9908     setbuf(debugFP, NULL);
9909   }
9910             DisplayError(buf, 0);
9911             return;
9912           case EndOfFile:
9913             if (boardIndex < backwardMostMove) {
9914                 /* Oops, gap.  How did that happen? */
9915                 DisplayError(_("Gap in move list"), 0);
9916                 return;
9917             }
9918             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9919             if (boardIndex > forwardMostMove) {
9920                 forwardMostMove = boardIndex;
9921             }
9922             return;
9923           case ElapsedTime:
9924             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9925                 strcat(parseList[boardIndex-1], " ");
9926                 strcat(parseList[boardIndex-1], yy_text);
9927             }
9928             continue;
9929           case Comment:
9930           case PGNTag:
9931           case NAG:
9932           default:
9933             /* ignore */
9934             continue;
9935           case WhiteWins:
9936           case BlackWins:
9937           case GameIsDrawn:
9938           case GameUnfinished:
9939             if (gameMode == IcsExamining) {
9940                 if (boardIndex < backwardMostMove) {
9941                     /* Oops, gap.  How did that happen? */
9942                     return;
9943                 }
9944                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9945                 return;
9946             }
9947             gameInfo.result = moveType;
9948             p = strchr(yy_text, '{');
9949             if (p == NULL) p = strchr(yy_text, '(');
9950             if (p == NULL) {
9951                 p = yy_text;
9952                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9953             } else {
9954                 q = strchr(p, *p == '{' ? '}' : ')');
9955                 if (q != NULL) *q = NULLCHAR;
9956                 p++;
9957             }
9958             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9959             gameInfo.resultDetails = StrSave(p);
9960             continue;
9961         }
9962         if (boardIndex >= forwardMostMove &&
9963             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9964             backwardMostMove = blackPlaysFirst ? 1 : 0;
9965             return;
9966         }
9967         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9968                                  fromY, fromX, toY, toX, promoChar,
9969                                  parseList[boardIndex]);
9970         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9971         /* currentMoveString is set as a side-effect of yylex */
9972         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9973         strcat(moveList[boardIndex], "\n");
9974         boardIndex++;
9975         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9976         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9977           case MT_NONE:
9978           case MT_STALEMATE:
9979           default:
9980             break;
9981           case MT_CHECK:
9982             if(!IS_SHOGI(gameInfo.variant))
9983                 strcat(parseList[boardIndex - 1], "+");
9984             break;
9985           case MT_CHECKMATE:
9986           case MT_STAINMATE:
9987             strcat(parseList[boardIndex - 1], "#");
9988             break;
9989         }
9990     }
9991 }
9992
9993
9994 /* Apply a move to the given board  */
9995 void
9996 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9997 {
9998   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
9999   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10000
10001     /* [HGM] compute & store e.p. status and castling rights for new position */
10002     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10003
10004       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10005       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10006       board[EP_STATUS] = EP_NONE;
10007       board[EP_FILE] = board[EP_RANK] = 100;
10008
10009   if (fromY == DROP_RANK) {
10010         /* must be first */
10011         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10012             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10013             return;
10014         }
10015         piece = board[toY][toX] = (ChessSquare) fromX;
10016   } else {
10017 //      ChessSquare victim;
10018       int i;
10019
10020       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10021 //           victim = board[killY][killX],
10022            killed = board[killY][killX],
10023            board[killY][killX] = EmptySquare,
10024            board[EP_STATUS] = EP_CAPTURE;
10025            if( kill2X >= 0 && kill2Y >= 0)
10026              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10027       }
10028
10029       if( board[toY][toX] != EmptySquare ) {
10030            board[EP_STATUS] = EP_CAPTURE;
10031            if( (fromX != toX || fromY != toY) && // not igui!
10032                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10033                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10034                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10035            }
10036       }
10037
10038       pawn = board[fromY][fromX];
10039       if( pawn == WhiteLance || pawn == BlackLance ) {
10040            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10041                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10042                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10043            }
10044       }
10045       if( pawn == WhitePawn ) {
10046            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10047                board[EP_STATUS] = EP_PAWN_MOVE;
10048            if( toY-fromY>=2) {
10049                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10050                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10051                         gameInfo.variant != VariantBerolina || toX < fromX)
10052                       board[EP_STATUS] = toX | berolina;
10053                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10054                         gameInfo.variant != VariantBerolina || toX > fromX)
10055                       board[EP_STATUS] = toX;
10056            }
10057       } else
10058       if( pawn == BlackPawn ) {
10059            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10060                board[EP_STATUS] = EP_PAWN_MOVE;
10061            if( toY-fromY<= -2) {
10062                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10063                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10064                         gameInfo.variant != VariantBerolina || toX < fromX)
10065                       board[EP_STATUS] = toX | berolina;
10066                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10067                         gameInfo.variant != VariantBerolina || toX > fromX)
10068                       board[EP_STATUS] = toX;
10069            }
10070        }
10071
10072        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10073        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10074        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10075        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10076
10077        for(i=0; i<nrCastlingRights; i++) {
10078            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10079               board[CASTLING][i] == toX   && castlingRank[i] == toY
10080              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10081        }
10082
10083        if(gameInfo.variant == VariantSChess) { // update virginity
10084            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10085            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10086            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10087            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10088        }
10089
10090      if (fromX == toX && fromY == toY) return;
10091
10092      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10093      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10094      if(gameInfo.variant == VariantKnightmate)
10095          king += (int) WhiteUnicorn - (int) WhiteKing;
10096
10097     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10098        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10099         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10100         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10101         board[EP_STATUS] = EP_NONE; // capture was fake!
10102     } else
10103     /* Code added by Tord: */
10104     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10105     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10106         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10107       board[EP_STATUS] = EP_NONE; // capture was fake!
10108       board[fromY][fromX] = EmptySquare;
10109       board[toY][toX] = EmptySquare;
10110       if((toX > fromX) != (piece == WhiteRook)) {
10111         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10112       } else {
10113         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10114       }
10115     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10116                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10117       board[EP_STATUS] = EP_NONE;
10118       board[fromY][fromX] = EmptySquare;
10119       board[toY][toX] = EmptySquare;
10120       if((toX > fromX) != (piece == BlackRook)) {
10121         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10122       } else {
10123         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10124       }
10125     /* End of code added by Tord */
10126
10127     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10128         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10129         board[toY][toX] = piece;
10130     } else if (board[fromY][fromX] == king
10131         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10132         && toY == fromY && toX > fromX+1) {
10133         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10134         board[fromY][toX-1] = board[fromY][rookX];
10135         board[fromY][rookX] = EmptySquare;
10136         board[fromY][fromX] = EmptySquare;
10137         board[toY][toX] = king;
10138     } else if (board[fromY][fromX] == king
10139         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10140                && toY == fromY && toX < fromX-1) {
10141         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10142         board[fromY][toX+1] = board[fromY][rookX];
10143         board[fromY][rookX] = EmptySquare;
10144         board[fromY][fromX] = EmptySquare;
10145         board[toY][toX] = king;
10146     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10147                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10148                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10149                ) {
10150         /* white pawn promotion */
10151         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10152         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10153             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10154         board[fromY][fromX] = EmptySquare;
10155     } else if ((fromY >= BOARD_HEIGHT>>1)
10156                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10157                && (toX != fromX)
10158                && gameInfo.variant != VariantXiangqi
10159                && gameInfo.variant != VariantBerolina
10160                && (pawn == WhitePawn)
10161                && (board[toY][toX] == EmptySquare)) {
10162         board[fromY][fromX] = EmptySquare;
10163         board[toY][toX] = piece;
10164         if(toY == epRank - 128 + 1)
10165             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10166         else
10167             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10168     } else if ((fromY == BOARD_HEIGHT-4)
10169                && (toX == fromX)
10170                && gameInfo.variant == VariantBerolina
10171                && (board[fromY][fromX] == WhitePawn)
10172                && (board[toY][toX] == EmptySquare)) {
10173         board[fromY][fromX] = EmptySquare;
10174         board[toY][toX] = WhitePawn;
10175         if(oldEP & EP_BEROLIN_A) {
10176                 captured = board[fromY][fromX-1];
10177                 board[fromY][fromX-1] = EmptySquare;
10178         }else{  captured = board[fromY][fromX+1];
10179                 board[fromY][fromX+1] = EmptySquare;
10180         }
10181     } else if (board[fromY][fromX] == king
10182         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10183                && toY == fromY && toX > fromX+1) {
10184         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10185         board[fromY][toX-1] = board[fromY][rookX];
10186         board[fromY][rookX] = EmptySquare;
10187         board[fromY][fromX] = EmptySquare;
10188         board[toY][toX] = king;
10189     } else if (board[fromY][fromX] == king
10190         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10191                && toY == fromY && toX < fromX-1) {
10192         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10193         board[fromY][toX+1] = board[fromY][rookX];
10194         board[fromY][rookX] = EmptySquare;
10195         board[fromY][fromX] = EmptySquare;
10196         board[toY][toX] = king;
10197     } else if (fromY == 7 && fromX == 3
10198                && board[fromY][fromX] == BlackKing
10199                && toY == 7 && toX == 5) {
10200         board[fromY][fromX] = EmptySquare;
10201         board[toY][toX] = BlackKing;
10202         board[fromY][7] = EmptySquare;
10203         board[toY][4] = BlackRook;
10204     } else if (fromY == 7 && fromX == 3
10205                && board[fromY][fromX] == BlackKing
10206                && toY == 7 && toX == 1) {
10207         board[fromY][fromX] = EmptySquare;
10208         board[toY][toX] = BlackKing;
10209         board[fromY][0] = EmptySquare;
10210         board[toY][2] = BlackRook;
10211     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10212                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10213                && toY < promoRank && promoChar
10214                ) {
10215         /* black pawn promotion */
10216         board[toY][toX] = CharToPiece(ToLower(promoChar));
10217         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10218             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10219         board[fromY][fromX] = EmptySquare;
10220     } else if ((fromY < BOARD_HEIGHT>>1)
10221                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10222                && (toX != fromX)
10223                && gameInfo.variant != VariantXiangqi
10224                && gameInfo.variant != VariantBerolina
10225                && (pawn == BlackPawn)
10226                && (board[toY][toX] == EmptySquare)) {
10227         board[fromY][fromX] = EmptySquare;
10228         board[toY][toX] = piece;
10229         if(toY == epRank - 128 - 1)
10230             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10231         else
10232             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10233     } else if ((fromY == 3)
10234                && (toX == fromX)
10235                && gameInfo.variant == VariantBerolina
10236                && (board[fromY][fromX] == BlackPawn)
10237                && (board[toY][toX] == EmptySquare)) {
10238         board[fromY][fromX] = EmptySquare;
10239         board[toY][toX] = BlackPawn;
10240         if(oldEP & EP_BEROLIN_A) {
10241                 captured = board[fromY][fromX-1];
10242                 board[fromY][fromX-1] = EmptySquare;
10243         }else{  captured = board[fromY][fromX+1];
10244                 board[fromY][fromX+1] = EmptySquare;
10245         }
10246     } else {
10247         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10248         board[fromY][fromX] = EmptySquare;
10249         board[toY][toX] = piece;
10250     }
10251   }
10252
10253     if (gameInfo.holdingsWidth != 0) {
10254
10255       /* !!A lot more code needs to be written to support holdings  */
10256       /* [HGM] OK, so I have written it. Holdings are stored in the */
10257       /* penultimate board files, so they are automaticlly stored   */
10258       /* in the game history.                                       */
10259       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10260                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10261         /* Delete from holdings, by decreasing count */
10262         /* and erasing image if necessary            */
10263         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10264         if(p < (int) BlackPawn) { /* white drop */
10265              p -= (int)WhitePawn;
10266                  p = PieceToNumber((ChessSquare)p);
10267              if(p >= gameInfo.holdingsSize) p = 0;
10268              if(--board[p][BOARD_WIDTH-2] <= 0)
10269                   board[p][BOARD_WIDTH-1] = EmptySquare;
10270              if((int)board[p][BOARD_WIDTH-2] < 0)
10271                         board[p][BOARD_WIDTH-2] = 0;
10272         } else {                  /* black drop */
10273              p -= (int)BlackPawn;
10274                  p = PieceToNumber((ChessSquare)p);
10275              if(p >= gameInfo.holdingsSize) p = 0;
10276              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10277                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10278              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10279                         board[BOARD_HEIGHT-1-p][1] = 0;
10280         }
10281       }
10282       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10283           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10284         /* [HGM] holdings: Add to holdings, if holdings exist */
10285         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10286                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10287                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10288         }
10289         p = (int) captured;
10290         if (p >= (int) BlackPawn) {
10291           p -= (int)BlackPawn;
10292           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10293                   /* Restore shogi-promoted piece to its original  first */
10294                   captured = (ChessSquare) (DEMOTED captured);
10295                   p = DEMOTED p;
10296           }
10297           p = PieceToNumber((ChessSquare)p);
10298           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10299           board[p][BOARD_WIDTH-2]++;
10300           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10301         } else {
10302           p -= (int)WhitePawn;
10303           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10304                   captured = (ChessSquare) (DEMOTED captured);
10305                   p = DEMOTED p;
10306           }
10307           p = PieceToNumber((ChessSquare)p);
10308           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10309           board[BOARD_HEIGHT-1-p][1]++;
10310           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10311         }
10312       }
10313     } else if (gameInfo.variant == VariantAtomic) {
10314       if (captured != EmptySquare) {
10315         int y, x;
10316         for (y = toY-1; y <= toY+1; y++) {
10317           for (x = toX-1; x <= toX+1; x++) {
10318             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10319                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10320               board[y][x] = EmptySquare;
10321             }
10322           }
10323         }
10324         board[toY][toX] = EmptySquare;
10325       }
10326     }
10327
10328     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10329         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10330     } else
10331     if(promoChar == '+') {
10332         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10333         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10334         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10335           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10336     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10337         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10338         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10339            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10340         board[toY][toX] = newPiece;
10341     }
10342     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10343                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10344         // [HGM] superchess: take promotion piece out of holdings
10345         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10346         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10347             if(!--board[k][BOARD_WIDTH-2])
10348                 board[k][BOARD_WIDTH-1] = EmptySquare;
10349         } else {
10350             if(!--board[BOARD_HEIGHT-1-k][1])
10351                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10352         }
10353     }
10354 }
10355
10356 /* Updates forwardMostMove */
10357 void
10358 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10359 {
10360     int x = toX, y = toY;
10361     char *s = parseList[forwardMostMove];
10362     ChessSquare p = boards[forwardMostMove][toY][toX];
10363 //    forwardMostMove++; // [HGM] bare: moved downstream
10364
10365     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10366     (void) CoordsToAlgebraic(boards[forwardMostMove],
10367                              PosFlags(forwardMostMove),
10368                              fromY, fromX, y, x, promoChar,
10369                              s);
10370     if(killX >= 0 && killY >= 0)
10371         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10372
10373     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10374         int timeLeft; static int lastLoadFlag=0; int king, piece;
10375         piece = boards[forwardMostMove][fromY][fromX];
10376         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10377         if(gameInfo.variant == VariantKnightmate)
10378             king += (int) WhiteUnicorn - (int) WhiteKing;
10379         if(forwardMostMove == 0) {
10380             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10381                 fprintf(serverMoves, "%s;", UserName());
10382             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10383                 fprintf(serverMoves, "%s;", second.tidy);
10384             fprintf(serverMoves, "%s;", first.tidy);
10385             if(gameMode == MachinePlaysWhite)
10386                 fprintf(serverMoves, "%s;", UserName());
10387             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10388                 fprintf(serverMoves, "%s;", second.tidy);
10389         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10390         lastLoadFlag = loadFlag;
10391         // print base move
10392         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10393         // print castling suffix
10394         if( toY == fromY && piece == king ) {
10395             if(toX-fromX > 1)
10396                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10397             if(fromX-toX >1)
10398                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10399         }
10400         // e.p. suffix
10401         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10402              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10403              boards[forwardMostMove][toY][toX] == EmptySquare
10404              && fromX != toX && fromY != toY)
10405                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10406         // promotion suffix
10407         if(promoChar != NULLCHAR) {
10408             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10409                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10410                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10411             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10412         }
10413         if(!loadFlag) {
10414                 char buf[MOVE_LEN*2], *p; int len;
10415             fprintf(serverMoves, "/%d/%d",
10416                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10417             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10418             else                      timeLeft = blackTimeRemaining/1000;
10419             fprintf(serverMoves, "/%d", timeLeft);
10420                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10421                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10422                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10423                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10424             fprintf(serverMoves, "/%s", buf);
10425         }
10426         fflush(serverMoves);
10427     }
10428
10429     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10430         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10431       return;
10432     }
10433     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10434     if (commentList[forwardMostMove+1] != NULL) {
10435         free(commentList[forwardMostMove+1]);
10436         commentList[forwardMostMove+1] = NULL;
10437     }
10438     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10439     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10440     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10441     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10442     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10443     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10444     adjustedClock = FALSE;
10445     gameInfo.result = GameUnfinished;
10446     if (gameInfo.resultDetails != NULL) {
10447         free(gameInfo.resultDetails);
10448         gameInfo.resultDetails = NULL;
10449     }
10450     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10451                               moveList[forwardMostMove - 1]);
10452     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10453       case MT_NONE:
10454       case MT_STALEMATE:
10455       default:
10456         break;
10457       case MT_CHECK:
10458         if(!IS_SHOGI(gameInfo.variant))
10459             strcat(parseList[forwardMostMove - 1], "+");
10460         break;
10461       case MT_CHECKMATE:
10462       case MT_STAINMATE:
10463         strcat(parseList[forwardMostMove - 1], "#");
10464         break;
10465     }
10466 }
10467
10468 /* Updates currentMove if not pausing */
10469 void
10470 ShowMove (int fromX, int fromY, int toX, int toY)
10471 {
10472     int instant = (gameMode == PlayFromGameFile) ?
10473         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10474     if(appData.noGUI) return;
10475     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10476         if (!instant) {
10477             if (forwardMostMove == currentMove + 1) {
10478                 AnimateMove(boards[forwardMostMove - 1],
10479                             fromX, fromY, toX, toY);
10480             }
10481         }
10482         currentMove = forwardMostMove;
10483     }
10484
10485     killX = killY = -1; // [HGM] lion: used up
10486
10487     if (instant) return;
10488
10489     DisplayMove(currentMove - 1);
10490     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10491             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10492                 SetHighlights(fromX, fromY, toX, toY);
10493             }
10494     }
10495     DrawPosition(FALSE, boards[currentMove]);
10496     DisplayBothClocks();
10497     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10498 }
10499
10500 void
10501 SendEgtPath (ChessProgramState *cps)
10502 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10503         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10504
10505         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10506
10507         while(*p) {
10508             char c, *q = name+1, *r, *s;
10509
10510             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10511             while(*p && *p != ',') *q++ = *p++;
10512             *q++ = ':'; *q = 0;
10513             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10514                 strcmp(name, ",nalimov:") == 0 ) {
10515                 // take nalimov path from the menu-changeable option first, if it is defined
10516               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10517                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10518             } else
10519             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10520                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10521                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10522                 s = r = StrStr(s, ":") + 1; // beginning of path info
10523                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10524                 c = *r; *r = 0;             // temporarily null-terminate path info
10525                     *--q = 0;               // strip of trailig ':' from name
10526                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10527                 *r = c;
10528                 SendToProgram(buf,cps);     // send egtbpath command for this format
10529             }
10530             if(*p == ',') p++; // read away comma to position for next format name
10531         }
10532 }
10533
10534 static int
10535 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10536 {
10537       int width = 8, height = 8, holdings = 0;             // most common sizes
10538       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10539       // correct the deviations default for each variant
10540       if( v == VariantXiangqi ) width = 9,  height = 10;
10541       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10542       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10543       if( v == VariantCapablanca || v == VariantCapaRandom ||
10544           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10545                                 width = 10;
10546       if( v == VariantCourier ) width = 12;
10547       if( v == VariantSuper )                            holdings = 8;
10548       if( v == VariantGreat )   width = 10,              holdings = 8;
10549       if( v == VariantSChess )                           holdings = 7;
10550       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10551       if( v == VariantChuChess) width = 10, height = 10;
10552       if( v == VariantChu )     width = 12, height = 12;
10553       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10554              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10555              holdingsSize >= 0 && holdingsSize != holdings;
10556 }
10557
10558 char variantError[MSG_SIZ];
10559
10560 char *
10561 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10562 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10563       char *p, *variant = VariantName(v);
10564       static char b[MSG_SIZ];
10565       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10566            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10567                                                holdingsSize, variant); // cook up sized variant name
10568            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10569            if(StrStr(list, b) == NULL) {
10570                // specific sized variant not known, check if general sizing allowed
10571                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10572                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10573                             boardWidth, boardHeight, holdingsSize, engine);
10574                    return NULL;
10575                }
10576                /* [HGM] here we really should compare with the maximum supported board size */
10577            }
10578       } else snprintf(b, MSG_SIZ,"%s", variant);
10579       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10580       p = StrStr(list, b);
10581       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10582       if(p == NULL) {
10583           // occurs not at all in list, or only as sub-string
10584           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10585           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10586               int l = strlen(variantError);
10587               char *q;
10588               while(p != list && p[-1] != ',') p--;
10589               q = strchr(p, ',');
10590               if(q) *q = NULLCHAR;
10591               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10592               if(q) *q= ',';
10593           }
10594           return NULL;
10595       }
10596       return b;
10597 }
10598
10599 void
10600 InitChessProgram (ChessProgramState *cps, int setup)
10601 /* setup needed to setup FRC opening position */
10602 {
10603     char buf[MSG_SIZ], *b;
10604     if (appData.noChessProgram) return;
10605     hintRequested = FALSE;
10606     bookRequested = FALSE;
10607
10608     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10609     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10610     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10611     if(cps->memSize) { /* [HGM] memory */
10612       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10613         SendToProgram(buf, cps);
10614     }
10615     SendEgtPath(cps); /* [HGM] EGT */
10616     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10617       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10618         SendToProgram(buf, cps);
10619     }
10620
10621     setboardSpoiledMachineBlack = FALSE;
10622     SendToProgram(cps->initString, cps);
10623     if (gameInfo.variant != VariantNormal &&
10624         gameInfo.variant != VariantLoadable
10625         /* [HGM] also send variant if board size non-standard */
10626         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10627
10628       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10629                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10630       if (b == NULL) {
10631         VariantClass v;
10632         char c, *q = cps->variants, *p = strchr(q, ',');
10633         if(p) *p = NULLCHAR;
10634         v = StringToVariant(q);
10635         DisplayError(variantError, 0);
10636         if(v != VariantUnknown && cps == &first) {
10637             int w, h, s;
10638             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10639                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10640             ASSIGN(appData.variant, q);
10641             Reset(TRUE, FALSE);
10642         }
10643         if(p) *p = ',';
10644         return;
10645       }
10646
10647       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10648       SendToProgram(buf, cps);
10649     }
10650     currentlyInitializedVariant = gameInfo.variant;
10651
10652     /* [HGM] send opening position in FRC to first engine */
10653     if(setup) {
10654           SendToProgram("force\n", cps);
10655           SendBoard(cps, 0);
10656           /* engine is now in force mode! Set flag to wake it up after first move. */
10657           setboardSpoiledMachineBlack = 1;
10658     }
10659
10660     if (cps->sendICS) {
10661       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10662       SendToProgram(buf, cps);
10663     }
10664     cps->maybeThinking = FALSE;
10665     cps->offeredDraw = 0;
10666     if (!appData.icsActive) {
10667         SendTimeControl(cps, movesPerSession, timeControl,
10668                         timeIncrement, appData.searchDepth,
10669                         searchTime);
10670     }
10671     if (appData.showThinking
10672         // [HGM] thinking: four options require thinking output to be sent
10673         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10674                                 ) {
10675         SendToProgram("post\n", cps);
10676     }
10677     SendToProgram("hard\n", cps);
10678     if (!appData.ponderNextMove) {
10679         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10680            it without being sure what state we are in first.  "hard"
10681            is not a toggle, so that one is OK.
10682          */
10683         SendToProgram("easy\n", cps);
10684     }
10685     if (cps->usePing) {
10686       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10687       SendToProgram(buf, cps);
10688     }
10689     cps->initDone = TRUE;
10690     ClearEngineOutputPane(cps == &second);
10691 }
10692
10693
10694 void
10695 ResendOptions (ChessProgramState *cps)
10696 { // send the stored value of the options
10697   int i;
10698   char buf[MSG_SIZ];
10699   Option *opt = cps->option;
10700   for(i=0; i<cps->nrOptions; i++, opt++) {
10701       switch(opt->type) {
10702         case Spin:
10703         case Slider:
10704         case CheckBox:
10705             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10706           break;
10707         case ComboBox:
10708           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10709           break;
10710         default:
10711             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10712           break;
10713         case Button:
10714         case SaveButton:
10715           continue;
10716       }
10717       SendToProgram(buf, cps);
10718   }
10719 }
10720
10721 void
10722 StartChessProgram (ChessProgramState *cps)
10723 {
10724     char buf[MSG_SIZ];
10725     int err;
10726
10727     if (appData.noChessProgram) return;
10728     cps->initDone = FALSE;
10729
10730     if (strcmp(cps->host, "localhost") == 0) {
10731         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10732     } else if (*appData.remoteShell == NULLCHAR) {
10733         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10734     } else {
10735         if (*appData.remoteUser == NULLCHAR) {
10736           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10737                     cps->program);
10738         } else {
10739           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10740                     cps->host, appData.remoteUser, cps->program);
10741         }
10742         err = StartChildProcess(buf, "", &cps->pr);
10743     }
10744
10745     if (err != 0) {
10746       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10747         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10748         if(cps != &first) return;
10749         appData.noChessProgram = TRUE;
10750         ThawUI();
10751         SetNCPMode();
10752 //      DisplayFatalError(buf, err, 1);
10753 //      cps->pr = NoProc;
10754 //      cps->isr = NULL;
10755         return;
10756     }
10757
10758     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10759     if (cps->protocolVersion > 1) {
10760       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10761       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10762         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10763         cps->comboCnt = 0;  //                and values of combo boxes
10764       }
10765       SendToProgram(buf, cps);
10766       if(cps->reload) ResendOptions(cps);
10767     } else {
10768       SendToProgram("xboard\n", cps);
10769     }
10770 }
10771
10772 void
10773 TwoMachinesEventIfReady P((void))
10774 {
10775   static int curMess = 0;
10776   if (first.lastPing != first.lastPong) {
10777     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10778     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10779     return;
10780   }
10781   if (second.lastPing != second.lastPong) {
10782     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10783     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10784     return;
10785   }
10786   DisplayMessage("", ""); curMess = 0;
10787   TwoMachinesEvent();
10788 }
10789
10790 char *
10791 MakeName (char *template)
10792 {
10793     time_t clock;
10794     struct tm *tm;
10795     static char buf[MSG_SIZ];
10796     char *p = buf;
10797     int i;
10798
10799     clock = time((time_t *)NULL);
10800     tm = localtime(&clock);
10801
10802     while(*p++ = *template++) if(p[-1] == '%') {
10803         switch(*template++) {
10804           case 0:   *p = 0; return buf;
10805           case 'Y': i = tm->tm_year+1900; break;
10806           case 'y': i = tm->tm_year-100; break;
10807           case 'M': i = tm->tm_mon+1; break;
10808           case 'd': i = tm->tm_mday; break;
10809           case 'h': i = tm->tm_hour; break;
10810           case 'm': i = tm->tm_min; break;
10811           case 's': i = tm->tm_sec; break;
10812           default:  i = 0;
10813         }
10814         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10815     }
10816     return buf;
10817 }
10818
10819 int
10820 CountPlayers (char *p)
10821 {
10822     int n = 0;
10823     while(p = strchr(p, '\n')) p++, n++; // count participants
10824     return n;
10825 }
10826
10827 FILE *
10828 WriteTourneyFile (char *results, FILE *f)
10829 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10830     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10831     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10832         // create a file with tournament description
10833         fprintf(f, "-participants {%s}\n", appData.participants);
10834         fprintf(f, "-seedBase %d\n", appData.seedBase);
10835         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10836         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10837         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10838         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10839         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10840         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10841         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10842         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10843         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10844         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10845         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10846         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10847         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10848         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10849         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10850         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10851         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10852         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10853         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10854         fprintf(f, "-smpCores %d\n", appData.smpCores);
10855         if(searchTime > 0)
10856                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10857         else {
10858                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10859                 fprintf(f, "-tc %s\n", appData.timeControl);
10860                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10861         }
10862         fprintf(f, "-results \"%s\"\n", results);
10863     }
10864     return f;
10865 }
10866
10867 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10868
10869 void
10870 Substitute (char *participants, int expunge)
10871 {
10872     int i, changed, changes=0, nPlayers=0;
10873     char *p, *q, *r, buf[MSG_SIZ];
10874     if(participants == NULL) return;
10875     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10876     r = p = participants; q = appData.participants;
10877     while(*p && *p == *q) {
10878         if(*p == '\n') r = p+1, nPlayers++;
10879         p++; q++;
10880     }
10881     if(*p) { // difference
10882         while(*p && *p++ != '\n');
10883         while(*q && *q++ != '\n');
10884       changed = nPlayers;
10885         changes = 1 + (strcmp(p, q) != 0);
10886     }
10887     if(changes == 1) { // a single engine mnemonic was changed
10888         q = r; while(*q) nPlayers += (*q++ == '\n');
10889         p = buf; while(*r && (*p = *r++) != '\n') p++;
10890         *p = NULLCHAR;
10891         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10892         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10893         if(mnemonic[i]) { // The substitute is valid
10894             FILE *f;
10895             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10896                 flock(fileno(f), LOCK_EX);
10897                 ParseArgsFromFile(f);
10898                 fseek(f, 0, SEEK_SET);
10899                 FREE(appData.participants); appData.participants = participants;
10900                 if(expunge) { // erase results of replaced engine
10901                     int len = strlen(appData.results), w, b, dummy;
10902                     for(i=0; i<len; i++) {
10903                         Pairing(i, nPlayers, &w, &b, &dummy);
10904                         if((w == changed || b == changed) && appData.results[i] == '*') {
10905                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10906                             fclose(f);
10907                             return;
10908                         }
10909                     }
10910                     for(i=0; i<len; i++) {
10911                         Pairing(i, nPlayers, &w, &b, &dummy);
10912                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10913                     }
10914                 }
10915                 WriteTourneyFile(appData.results, f);
10916                 fclose(f); // release lock
10917                 return;
10918             }
10919         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10920     }
10921     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10922     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10923     free(participants);
10924     return;
10925 }
10926
10927 int
10928 CheckPlayers (char *participants)
10929 {
10930         int i;
10931         char buf[MSG_SIZ], *p;
10932         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10933         while(p = strchr(participants, '\n')) {
10934             *p = NULLCHAR;
10935             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10936             if(!mnemonic[i]) {
10937                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10938                 *p = '\n';
10939                 DisplayError(buf, 0);
10940                 return 1;
10941             }
10942             *p = '\n';
10943             participants = p + 1;
10944         }
10945         return 0;
10946 }
10947
10948 int
10949 CreateTourney (char *name)
10950 {
10951         FILE *f;
10952         if(matchMode && strcmp(name, appData.tourneyFile)) {
10953              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10954         }
10955         if(name[0] == NULLCHAR) {
10956             if(appData.participants[0])
10957                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10958             return 0;
10959         }
10960         f = fopen(name, "r");
10961         if(f) { // file exists
10962             ASSIGN(appData.tourneyFile, name);
10963             ParseArgsFromFile(f); // parse it
10964         } else {
10965             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10966             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10967                 DisplayError(_("Not enough participants"), 0);
10968                 return 0;
10969             }
10970             if(CheckPlayers(appData.participants)) return 0;
10971             ASSIGN(appData.tourneyFile, name);
10972             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10973             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10974         }
10975         fclose(f);
10976         appData.noChessProgram = FALSE;
10977         appData.clockMode = TRUE;
10978         SetGNUMode();
10979         return 1;
10980 }
10981
10982 int
10983 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10984 {
10985     char buf[MSG_SIZ], *p, *q;
10986     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10987     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10988     skip = !all && group[0]; // if group requested, we start in skip mode
10989     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10990         p = names; q = buf; header = 0;
10991         while(*p && *p != '\n') *q++ = *p++;
10992         *q = 0;
10993         if(*p == '\n') p++;
10994         if(buf[0] == '#') {
10995             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10996             depth++; // we must be entering a new group
10997             if(all) continue; // suppress printing group headers when complete list requested
10998             header = 1;
10999             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11000         }
11001         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11002         if(engineList[i]) free(engineList[i]);
11003         engineList[i] = strdup(buf);
11004         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11005         if(engineMnemonic[i]) free(engineMnemonic[i]);
11006         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11007             strcat(buf, " (");
11008             sscanf(q + 8, "%s", buf + strlen(buf));
11009             strcat(buf, ")");
11010         }
11011         engineMnemonic[i] = strdup(buf);
11012         i++;
11013     }
11014     engineList[i] = engineMnemonic[i] = NULL;
11015     return i;
11016 }
11017
11018 // following implemented as macro to avoid type limitations
11019 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11020
11021 void
11022 SwapEngines (int n)
11023 {   // swap settings for first engine and other engine (so far only some selected options)
11024     int h;
11025     char *p;
11026     if(n == 0) return;
11027     SWAP(directory, p)
11028     SWAP(chessProgram, p)
11029     SWAP(isUCI, h)
11030     SWAP(hasOwnBookUCI, h)
11031     SWAP(protocolVersion, h)
11032     SWAP(reuse, h)
11033     SWAP(scoreIsAbsolute, h)
11034     SWAP(timeOdds, h)
11035     SWAP(logo, p)
11036     SWAP(pgnName, p)
11037     SWAP(pvSAN, h)
11038     SWAP(engOptions, p)
11039     SWAP(engInitString, p)
11040     SWAP(computerString, p)
11041     SWAP(features, p)
11042     SWAP(fenOverride, p)
11043     SWAP(NPS, h)
11044     SWAP(accumulateTC, h)
11045     SWAP(drawDepth, h)
11046     SWAP(host, p)
11047     SWAP(pseudo, h)
11048 }
11049
11050 int
11051 GetEngineLine (char *s, int n)
11052 {
11053     int i;
11054     char buf[MSG_SIZ];
11055     extern char *icsNames;
11056     if(!s || !*s) return 0;
11057     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11058     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11059     if(!mnemonic[i]) return 0;
11060     if(n == 11) return 1; // just testing if there was a match
11061     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11062     if(n == 1) SwapEngines(n);
11063     ParseArgsFromString(buf);
11064     if(n == 1) SwapEngines(n);
11065     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11066         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11067         ParseArgsFromString(buf);
11068     }
11069     return 1;
11070 }
11071
11072 int
11073 SetPlayer (int player, char *p)
11074 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11075     int i;
11076     char buf[MSG_SIZ], *engineName;
11077     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11078     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11079     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11080     if(mnemonic[i]) {
11081         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11082         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11083         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11084         ParseArgsFromString(buf);
11085     } else { // no engine with this nickname is installed!
11086         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11087         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11088         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11089         ModeHighlight();
11090         DisplayError(buf, 0);
11091         return 0;
11092     }
11093     free(engineName);
11094     return i;
11095 }
11096
11097 char *recentEngines;
11098
11099 void
11100 RecentEngineEvent (int nr)
11101 {
11102     int n;
11103 //    SwapEngines(1); // bump first to second
11104 //    ReplaceEngine(&second, 1); // and load it there
11105     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11106     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11107     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11108         ReplaceEngine(&first, 0);
11109         FloatToFront(&appData.recentEngineList, command[n]);
11110     }
11111 }
11112
11113 int
11114 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11115 {   // determine players from game number
11116     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11117
11118     if(appData.tourneyType == 0) {
11119         roundsPerCycle = (nPlayers - 1) | 1;
11120         pairingsPerRound = nPlayers / 2;
11121     } else if(appData.tourneyType > 0) {
11122         roundsPerCycle = nPlayers - appData.tourneyType;
11123         pairingsPerRound = appData.tourneyType;
11124     }
11125     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11126     gamesPerCycle = gamesPerRound * roundsPerCycle;
11127     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11128     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11129     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11130     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11131     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11132     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11133
11134     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11135     if(appData.roundSync) *syncInterval = gamesPerRound;
11136
11137     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11138
11139     if(appData.tourneyType == 0) {
11140         if(curPairing == (nPlayers-1)/2 ) {
11141             *whitePlayer = curRound;
11142             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11143         } else {
11144             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11145             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11146             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11147             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11148         }
11149     } else if(appData.tourneyType > 1) {
11150         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11151         *whitePlayer = curRound + appData.tourneyType;
11152     } else if(appData.tourneyType > 0) {
11153         *whitePlayer = curPairing;
11154         *blackPlayer = curRound + appData.tourneyType;
11155     }
11156
11157     // take care of white/black alternation per round.
11158     // For cycles and games this is already taken care of by default, derived from matchGame!
11159     return curRound & 1;
11160 }
11161
11162 int
11163 NextTourneyGame (int nr, int *swapColors)
11164 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11165     char *p, *q;
11166     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11167     FILE *tf;
11168     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11169     tf = fopen(appData.tourneyFile, "r");
11170     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11171     ParseArgsFromFile(tf); fclose(tf);
11172     InitTimeControls(); // TC might be altered from tourney file
11173
11174     nPlayers = CountPlayers(appData.participants); // count participants
11175     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11176     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11177
11178     if(syncInterval) {
11179         p = q = appData.results;
11180         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11181         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11182             DisplayMessage(_("Waiting for other game(s)"),"");
11183             waitingForGame = TRUE;
11184             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11185             return 0;
11186         }
11187         waitingForGame = FALSE;
11188     }
11189
11190     if(appData.tourneyType < 0) {
11191         if(nr>=0 && !pairingReceived) {
11192             char buf[1<<16];
11193             if(pairing.pr == NoProc) {
11194                 if(!appData.pairingEngine[0]) {
11195                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11196                     return 0;
11197                 }
11198                 StartChessProgram(&pairing); // starts the pairing engine
11199             }
11200             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11201             SendToProgram(buf, &pairing);
11202             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11203             SendToProgram(buf, &pairing);
11204             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11205         }
11206         pairingReceived = 0;                              // ... so we continue here
11207         *swapColors = 0;
11208         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11209         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11210         matchGame = 1; roundNr = nr / syncInterval + 1;
11211     }
11212
11213     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11214
11215     // redefine engines, engine dir, etc.
11216     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11217     if(first.pr == NoProc) {
11218       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11219       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11220     }
11221     if(second.pr == NoProc) {
11222       SwapEngines(1);
11223       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11224       SwapEngines(1);         // and make that valid for second engine by swapping
11225       InitEngine(&second, 1);
11226     }
11227     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11228     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11229     return OK;
11230 }
11231
11232 void
11233 NextMatchGame ()
11234 {   // performs game initialization that does not invoke engines, and then tries to start the game
11235     int res, firstWhite, swapColors = 0;
11236     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11237     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
11238         char buf[MSG_SIZ];
11239         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11240         if(strcmp(buf, currentDebugFile)) { // name has changed
11241             FILE *f = fopen(buf, "w");
11242             if(f) { // if opening the new file failed, just keep using the old one
11243                 ASSIGN(currentDebugFile, buf);
11244                 fclose(debugFP);
11245                 debugFP = f;
11246             }
11247             if(appData.serverFileName) {
11248                 if(serverFP) fclose(serverFP);
11249                 serverFP = fopen(appData.serverFileName, "w");
11250                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11251                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11252             }
11253         }
11254     }
11255     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11256     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11257     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11258     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11259     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11260     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11261     Reset(FALSE, first.pr != NoProc);
11262     res = LoadGameOrPosition(matchGame); // setup game
11263     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11264     if(!res) return; // abort when bad game/pos file
11265     TwoMachinesEvent();
11266 }
11267
11268 void
11269 UserAdjudicationEvent (int result)
11270 {
11271     ChessMove gameResult = GameIsDrawn;
11272
11273     if( result > 0 ) {
11274         gameResult = WhiteWins;
11275     }
11276     else if( result < 0 ) {
11277         gameResult = BlackWins;
11278     }
11279
11280     if( gameMode == TwoMachinesPlay ) {
11281         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11282     }
11283 }
11284
11285
11286 // [HGM] save: calculate checksum of game to make games easily identifiable
11287 int
11288 StringCheckSum (char *s)
11289 {
11290         int i = 0;
11291         if(s==NULL) return 0;
11292         while(*s) i = i*259 + *s++;
11293         return i;
11294 }
11295
11296 int
11297 GameCheckSum ()
11298 {
11299         int i, sum=0;
11300         for(i=backwardMostMove; i<forwardMostMove; i++) {
11301                 sum += pvInfoList[i].depth;
11302                 sum += StringCheckSum(parseList[i]);
11303                 sum += StringCheckSum(commentList[i]);
11304                 sum *= 261;
11305         }
11306         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11307         return sum + StringCheckSum(commentList[i]);
11308 } // end of save patch
11309
11310 void
11311 GameEnds (ChessMove result, char *resultDetails, int whosays)
11312 {
11313     GameMode nextGameMode;
11314     int isIcsGame;
11315     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11316
11317     if(endingGame) return; /* [HGM] crash: forbid recursion */
11318     endingGame = 1;
11319     if(twoBoards) { // [HGM] dual: switch back to one board
11320         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11321         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11322     }
11323     if (appData.debugMode) {
11324       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11325               result, resultDetails ? resultDetails : "(null)", whosays);
11326     }
11327
11328     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11329
11330     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11331
11332     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11333         /* If we are playing on ICS, the server decides when the
11334            game is over, but the engine can offer to draw, claim
11335            a draw, or resign.
11336          */
11337 #if ZIPPY
11338         if (appData.zippyPlay && first.initDone) {
11339             if (result == GameIsDrawn) {
11340                 /* In case draw still needs to be claimed */
11341                 SendToICS(ics_prefix);
11342                 SendToICS("draw\n");
11343             } else if (StrCaseStr(resultDetails, "resign")) {
11344                 SendToICS(ics_prefix);
11345                 SendToICS("resign\n");
11346             }
11347         }
11348 #endif
11349         endingGame = 0; /* [HGM] crash */
11350         return;
11351     }
11352
11353     /* If we're loading the game from a file, stop */
11354     if (whosays == GE_FILE) {
11355       (void) StopLoadGameTimer();
11356       gameFileFP = NULL;
11357     }
11358
11359     /* Cancel draw offers */
11360     first.offeredDraw = second.offeredDraw = 0;
11361
11362     /* If this is an ICS game, only ICS can really say it's done;
11363        if not, anyone can. */
11364     isIcsGame = (gameMode == IcsPlayingWhite ||
11365                  gameMode == IcsPlayingBlack ||
11366                  gameMode == IcsObserving    ||
11367                  gameMode == IcsExamining);
11368
11369     if (!isIcsGame || whosays == GE_ICS) {
11370         /* OK -- not an ICS game, or ICS said it was done */
11371         StopClocks();
11372         if (!isIcsGame && !appData.noChessProgram)
11373           SetUserThinkingEnables();
11374
11375         /* [HGM] if a machine claims the game end we verify this claim */
11376         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11377             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11378                 char claimer;
11379                 ChessMove trueResult = (ChessMove) -1;
11380
11381                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11382                                             first.twoMachinesColor[0] :
11383                                             second.twoMachinesColor[0] ;
11384
11385                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11386                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11387                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11388                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11389                 } else
11390                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11391                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11392                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11393                 } else
11394                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11395                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11396                 }
11397
11398                 // now verify win claims, but not in drop games, as we don't understand those yet
11399                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11400                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11401                     (result == WhiteWins && claimer == 'w' ||
11402                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11403                       if (appData.debugMode) {
11404                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11405                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11406                       }
11407                       if(result != trueResult) {
11408                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11409                               result = claimer == 'w' ? BlackWins : WhiteWins;
11410                               resultDetails = buf;
11411                       }
11412                 } else
11413                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11414                     && (forwardMostMove <= backwardMostMove ||
11415                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11416                         (claimer=='b')==(forwardMostMove&1))
11417                                                                                   ) {
11418                       /* [HGM] verify: draws that were not flagged are false claims */
11419                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11420                       result = claimer == 'w' ? BlackWins : WhiteWins;
11421                       resultDetails = buf;
11422                 }
11423                 /* (Claiming a loss is accepted no questions asked!) */
11424             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11425                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11426                 result = GameUnfinished;
11427                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11428             }
11429             /* [HGM] bare: don't allow bare King to win */
11430             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11431                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11432                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11433                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11434                && result != GameIsDrawn)
11435             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11436                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11437                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11438                         if(p >= 0 && p <= (int)WhiteKing) k++;
11439                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11440                 }
11441                 if (appData.debugMode) {
11442                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11443                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11444                 }
11445                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11446                         result = GameIsDrawn;
11447                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11448                         resultDetails = buf;
11449                 }
11450             }
11451         }
11452
11453
11454         if(serverMoves != NULL && !loadFlag) { char c = '=';
11455             if(result==WhiteWins) c = '+';
11456             if(result==BlackWins) c = '-';
11457             if(resultDetails != NULL)
11458                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11459         }
11460         if (resultDetails != NULL) {
11461             gameInfo.result = result;
11462             gameInfo.resultDetails = StrSave(resultDetails);
11463
11464             /* display last move only if game was not loaded from file */
11465             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11466                 DisplayMove(currentMove - 1);
11467
11468             if (forwardMostMove != 0) {
11469                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11470                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11471                                                                 ) {
11472                     if (*appData.saveGameFile != NULLCHAR) {
11473                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11474                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11475                         else
11476                         SaveGameToFile(appData.saveGameFile, TRUE);
11477                     } else if (appData.autoSaveGames) {
11478                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11479                     }
11480                     if (*appData.savePositionFile != NULLCHAR) {
11481                         SavePositionToFile(appData.savePositionFile);
11482                     }
11483                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11484                 }
11485             }
11486
11487             /* Tell program how game ended in case it is learning */
11488             /* [HGM] Moved this to after saving the PGN, just in case */
11489             /* engine died and we got here through time loss. In that */
11490             /* case we will get a fatal error writing the pipe, which */
11491             /* would otherwise lose us the PGN.                       */
11492             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11493             /* output during GameEnds should never be fatal anymore   */
11494             if (gameMode == MachinePlaysWhite ||
11495                 gameMode == MachinePlaysBlack ||
11496                 gameMode == TwoMachinesPlay ||
11497                 gameMode == IcsPlayingWhite ||
11498                 gameMode == IcsPlayingBlack ||
11499                 gameMode == BeginningOfGame) {
11500                 char buf[MSG_SIZ];
11501                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11502                         resultDetails);
11503                 if (first.pr != NoProc) {
11504                     SendToProgram(buf, &first);
11505                 }
11506                 if (second.pr != NoProc &&
11507                     gameMode == TwoMachinesPlay) {
11508                     SendToProgram(buf, &second);
11509                 }
11510             }
11511         }
11512
11513         if (appData.icsActive) {
11514             if (appData.quietPlay &&
11515                 (gameMode == IcsPlayingWhite ||
11516                  gameMode == IcsPlayingBlack)) {
11517                 SendToICS(ics_prefix);
11518                 SendToICS("set shout 1\n");
11519             }
11520             nextGameMode = IcsIdle;
11521             ics_user_moved = FALSE;
11522             /* clean up premove.  It's ugly when the game has ended and the
11523              * premove highlights are still on the board.
11524              */
11525             if (gotPremove) {
11526               gotPremove = FALSE;
11527               ClearPremoveHighlights();
11528               DrawPosition(FALSE, boards[currentMove]);
11529             }
11530             if (whosays == GE_ICS) {
11531                 switch (result) {
11532                 case WhiteWins:
11533                     if (gameMode == IcsPlayingWhite)
11534                         PlayIcsWinSound();
11535                     else if(gameMode == IcsPlayingBlack)
11536                         PlayIcsLossSound();
11537                     break;
11538                 case BlackWins:
11539                     if (gameMode == IcsPlayingBlack)
11540                         PlayIcsWinSound();
11541                     else if(gameMode == IcsPlayingWhite)
11542                         PlayIcsLossSound();
11543                     break;
11544                 case GameIsDrawn:
11545                     PlayIcsDrawSound();
11546                     break;
11547                 default:
11548                     PlayIcsUnfinishedSound();
11549                 }
11550             }
11551             if(appData.quitNext) { ExitEvent(0); return; }
11552         } else if (gameMode == EditGame ||
11553                    gameMode == PlayFromGameFile ||
11554                    gameMode == AnalyzeMode ||
11555                    gameMode == AnalyzeFile) {
11556             nextGameMode = gameMode;
11557         } else {
11558             nextGameMode = EndOfGame;
11559         }
11560         pausing = FALSE;
11561         ModeHighlight();
11562     } else {
11563         nextGameMode = gameMode;
11564     }
11565
11566     if (appData.noChessProgram) {
11567         gameMode = nextGameMode;
11568         ModeHighlight();
11569         endingGame = 0; /* [HGM] crash */
11570         return;
11571     }
11572
11573     if (first.reuse) {
11574         /* Put first chess program into idle state */
11575         if (first.pr != NoProc &&
11576             (gameMode == MachinePlaysWhite ||
11577              gameMode == MachinePlaysBlack ||
11578              gameMode == TwoMachinesPlay ||
11579              gameMode == IcsPlayingWhite ||
11580              gameMode == IcsPlayingBlack ||
11581              gameMode == BeginningOfGame)) {
11582             SendToProgram("force\n", &first);
11583             if (first.usePing) {
11584               char buf[MSG_SIZ];
11585               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11586               SendToProgram(buf, &first);
11587             }
11588         }
11589     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11590         /* Kill off first chess program */
11591         if (first.isr != NULL)
11592           RemoveInputSource(first.isr);
11593         first.isr = NULL;
11594
11595         if (first.pr != NoProc) {
11596             ExitAnalyzeMode();
11597             DoSleep( appData.delayBeforeQuit );
11598             SendToProgram("quit\n", &first);
11599             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11600             first.reload = TRUE;
11601         }
11602         first.pr = NoProc;
11603     }
11604     if (second.reuse) {
11605         /* Put second chess program into idle state */
11606         if (second.pr != NoProc &&
11607             gameMode == TwoMachinesPlay) {
11608             SendToProgram("force\n", &second);
11609             if (second.usePing) {
11610               char buf[MSG_SIZ];
11611               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11612               SendToProgram(buf, &second);
11613             }
11614         }
11615     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11616         /* Kill off second chess program */
11617         if (second.isr != NULL)
11618           RemoveInputSource(second.isr);
11619         second.isr = NULL;
11620
11621         if (second.pr != NoProc) {
11622             DoSleep( appData.delayBeforeQuit );
11623             SendToProgram("quit\n", &second);
11624             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11625             second.reload = TRUE;
11626         }
11627         second.pr = NoProc;
11628     }
11629
11630     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11631         char resChar = '=';
11632         switch (result) {
11633         case WhiteWins:
11634           resChar = '+';
11635           if (first.twoMachinesColor[0] == 'w') {
11636             first.matchWins++;
11637           } else {
11638             second.matchWins++;
11639           }
11640           break;
11641         case BlackWins:
11642           resChar = '-';
11643           if (first.twoMachinesColor[0] == 'b') {
11644             first.matchWins++;
11645           } else {
11646             second.matchWins++;
11647           }
11648           break;
11649         case GameUnfinished:
11650           resChar = ' ';
11651         default:
11652           break;
11653         }
11654
11655         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11656         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11657             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11658             ReserveGame(nextGame, resChar); // sets nextGame
11659             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11660             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11661         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11662
11663         if (nextGame <= appData.matchGames && !abortMatch) {
11664             gameMode = nextGameMode;
11665             matchGame = nextGame; // this will be overruled in tourney mode!
11666             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11667             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11668             endingGame = 0; /* [HGM] crash */
11669             return;
11670         } else {
11671             gameMode = nextGameMode;
11672             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11673                      first.tidy, second.tidy,
11674                      first.matchWins, second.matchWins,
11675                      appData.matchGames - (first.matchWins + second.matchWins));
11676             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11677             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11678             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11679             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11680                 first.twoMachinesColor = "black\n";
11681                 second.twoMachinesColor = "white\n";
11682             } else {
11683                 first.twoMachinesColor = "white\n";
11684                 second.twoMachinesColor = "black\n";
11685             }
11686         }
11687     }
11688     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11689         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11690       ExitAnalyzeMode();
11691     gameMode = nextGameMode;
11692     ModeHighlight();
11693     endingGame = 0;  /* [HGM] crash */
11694     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11695         if(matchMode == TRUE) { // match through command line: exit with or without popup
11696             if(ranking) {
11697                 ToNrEvent(forwardMostMove);
11698                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11699                 else ExitEvent(0);
11700             } else DisplayFatalError(buf, 0, 0);
11701         } else { // match through menu; just stop, with or without popup
11702             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11703             ModeHighlight();
11704             if(ranking){
11705                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11706             } else DisplayNote(buf);
11707       }
11708       if(ranking) free(ranking);
11709     }
11710 }
11711
11712 /* Assumes program was just initialized (initString sent).
11713    Leaves program in force mode. */
11714 void
11715 FeedMovesToProgram (ChessProgramState *cps, int upto)
11716 {
11717     int i;
11718
11719     if (appData.debugMode)
11720       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11721               startedFromSetupPosition ? "position and " : "",
11722               backwardMostMove, upto, cps->which);
11723     if(currentlyInitializedVariant != gameInfo.variant) {
11724       char buf[MSG_SIZ];
11725         // [HGM] variantswitch: make engine aware of new variant
11726         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11727                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11728                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11729         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11730         SendToProgram(buf, cps);
11731         currentlyInitializedVariant = gameInfo.variant;
11732     }
11733     SendToProgram("force\n", cps);
11734     if (startedFromSetupPosition) {
11735         SendBoard(cps, backwardMostMove);
11736     if (appData.debugMode) {
11737         fprintf(debugFP, "feedMoves\n");
11738     }
11739     }
11740     for (i = backwardMostMove; i < upto; i++) {
11741         SendMoveToProgram(i, cps);
11742     }
11743 }
11744
11745
11746 int
11747 ResurrectChessProgram ()
11748 {
11749      /* The chess program may have exited.
11750         If so, restart it and feed it all the moves made so far. */
11751     static int doInit = 0;
11752
11753     if (appData.noChessProgram) return 1;
11754
11755     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11756         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11757         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11758         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11759     } else {
11760         if (first.pr != NoProc) return 1;
11761         StartChessProgram(&first);
11762     }
11763     InitChessProgram(&first, FALSE);
11764     FeedMovesToProgram(&first, currentMove);
11765
11766     if (!first.sendTime) {
11767         /* can't tell gnuchess what its clock should read,
11768            so we bow to its notion. */
11769         ResetClocks();
11770         timeRemaining[0][currentMove] = whiteTimeRemaining;
11771         timeRemaining[1][currentMove] = blackTimeRemaining;
11772     }
11773
11774     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11775                 appData.icsEngineAnalyze) && first.analysisSupport) {
11776       SendToProgram("analyze\n", &first);
11777       first.analyzing = TRUE;
11778     }
11779     return 1;
11780 }
11781
11782 /*
11783  * Button procedures
11784  */
11785 void
11786 Reset (int redraw, int init)
11787 {
11788     int i;
11789
11790     if (appData.debugMode) {
11791         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11792                 redraw, init, gameMode);
11793     }
11794     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11795     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11796     CleanupTail(); // [HGM] vari: delete any stored variations
11797     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11798     pausing = pauseExamInvalid = FALSE;
11799     startedFromSetupPosition = blackPlaysFirst = FALSE;
11800     firstMove = TRUE;
11801     whiteFlag = blackFlag = FALSE;
11802     userOfferedDraw = FALSE;
11803     hintRequested = bookRequested = FALSE;
11804     first.maybeThinking = FALSE;
11805     second.maybeThinking = FALSE;
11806     first.bookSuspend = FALSE; // [HGM] book
11807     second.bookSuspend = FALSE;
11808     thinkOutput[0] = NULLCHAR;
11809     lastHint[0] = NULLCHAR;
11810     ClearGameInfo(&gameInfo);
11811     gameInfo.variant = StringToVariant(appData.variant);
11812     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11813     ics_user_moved = ics_clock_paused = FALSE;
11814     ics_getting_history = H_FALSE;
11815     ics_gamenum = -1;
11816     white_holding[0] = black_holding[0] = NULLCHAR;
11817     ClearProgramStats();
11818     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11819
11820     ResetFrontEnd();
11821     ClearHighlights();
11822     flipView = appData.flipView;
11823     ClearPremoveHighlights();
11824     gotPremove = FALSE;
11825     alarmSounded = FALSE;
11826     killX = killY = -1; // [HGM] lion
11827
11828     GameEnds(EndOfFile, NULL, GE_PLAYER);
11829     if(appData.serverMovesName != NULL) {
11830         /* [HGM] prepare to make moves file for broadcasting */
11831         clock_t t = clock();
11832         if(serverMoves != NULL) fclose(serverMoves);
11833         serverMoves = fopen(appData.serverMovesName, "r");
11834         if(serverMoves != NULL) {
11835             fclose(serverMoves);
11836             /* delay 15 sec before overwriting, so all clients can see end */
11837             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11838         }
11839         serverMoves = fopen(appData.serverMovesName, "w");
11840     }
11841
11842     ExitAnalyzeMode();
11843     gameMode = BeginningOfGame;
11844     ModeHighlight();
11845     if(appData.icsActive) gameInfo.variant = VariantNormal;
11846     currentMove = forwardMostMove = backwardMostMove = 0;
11847     MarkTargetSquares(1);
11848     InitPosition(redraw);
11849     for (i = 0; i < MAX_MOVES; i++) {
11850         if (commentList[i] != NULL) {
11851             free(commentList[i]);
11852             commentList[i] = NULL;
11853         }
11854     }
11855     ResetClocks();
11856     timeRemaining[0][0] = whiteTimeRemaining;
11857     timeRemaining[1][0] = blackTimeRemaining;
11858
11859     if (first.pr == NoProc) {
11860         StartChessProgram(&first);
11861     }
11862     if (init) {
11863             InitChessProgram(&first, startedFromSetupPosition);
11864     }
11865     DisplayTitle("");
11866     DisplayMessage("", "");
11867     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11868     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11869     ClearMap();        // [HGM] exclude: invalidate map
11870 }
11871
11872 void
11873 AutoPlayGameLoop ()
11874 {
11875     for (;;) {
11876         if (!AutoPlayOneMove())
11877           return;
11878         if (matchMode || appData.timeDelay == 0)
11879           continue;
11880         if (appData.timeDelay < 0)
11881           return;
11882         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11883         break;
11884     }
11885 }
11886
11887 void
11888 AnalyzeNextGame()
11889 {
11890     ReloadGame(1); // next game
11891 }
11892
11893 int
11894 AutoPlayOneMove ()
11895 {
11896     int fromX, fromY, toX, toY;
11897
11898     if (appData.debugMode) {
11899       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11900     }
11901
11902     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11903       return FALSE;
11904
11905     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11906       pvInfoList[currentMove].depth = programStats.depth;
11907       pvInfoList[currentMove].score = programStats.score;
11908       pvInfoList[currentMove].time  = 0;
11909       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11910       else { // append analysis of final position as comment
11911         char buf[MSG_SIZ];
11912         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11913         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11914       }
11915       programStats.depth = 0;
11916     }
11917
11918     if (currentMove >= forwardMostMove) {
11919       if(gameMode == AnalyzeFile) {
11920           if(appData.loadGameIndex == -1) {
11921             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11922           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11923           } else {
11924           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11925         }
11926       }
11927 //      gameMode = EndOfGame;
11928 //      ModeHighlight();
11929
11930       /* [AS] Clear current move marker at the end of a game */
11931       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11932
11933       return FALSE;
11934     }
11935
11936     toX = moveList[currentMove][2] - AAA;
11937     toY = moveList[currentMove][3] - ONE;
11938
11939     if (moveList[currentMove][1] == '@') {
11940         if (appData.highlightLastMove) {
11941             SetHighlights(-1, -1, toX, toY);
11942         }
11943     } else {
11944         int viaX = moveList[currentMove][5] - AAA;
11945         int viaY = moveList[currentMove][6] - ONE;
11946         fromX = moveList[currentMove][0] - AAA;
11947         fromY = moveList[currentMove][1] - ONE;
11948
11949         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11950
11951         if(moveList[currentMove][4] == ';') { // multi-leg
11952             ChessSquare piece = boards[currentMove][viaY][viaX];
11953             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11954             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11955             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11956             boards[currentMove][viaY][viaX] = piece;
11957         } else
11958         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11959
11960         if (appData.highlightLastMove) {
11961             SetHighlights(fromX, fromY, toX, toY);
11962         }
11963     }
11964     DisplayMove(currentMove);
11965     SendMoveToProgram(currentMove++, &first);
11966     DisplayBothClocks();
11967     DrawPosition(FALSE, boards[currentMove]);
11968     // [HGM] PV info: always display, routine tests if empty
11969     DisplayComment(currentMove - 1, commentList[currentMove]);
11970     return TRUE;
11971 }
11972
11973
11974 int
11975 LoadGameOneMove (ChessMove readAhead)
11976 {
11977     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11978     char promoChar = NULLCHAR;
11979     ChessMove moveType;
11980     char move[MSG_SIZ];
11981     char *p, *q;
11982
11983     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11984         gameMode != AnalyzeMode && gameMode != Training) {
11985         gameFileFP = NULL;
11986         return FALSE;
11987     }
11988
11989     yyboardindex = forwardMostMove;
11990     if (readAhead != EndOfFile) {
11991       moveType = readAhead;
11992     } else {
11993       if (gameFileFP == NULL)
11994           return FALSE;
11995       moveType = (ChessMove) Myylex();
11996     }
11997
11998     done = FALSE;
11999     switch (moveType) {
12000       case Comment:
12001         if (appData.debugMode)
12002           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12003         p = yy_text;
12004
12005         /* append the comment but don't display it */
12006         AppendComment(currentMove, p, FALSE);
12007         return TRUE;
12008
12009       case WhiteCapturesEnPassant:
12010       case BlackCapturesEnPassant:
12011       case WhitePromotion:
12012       case BlackPromotion:
12013       case WhiteNonPromotion:
12014       case BlackNonPromotion:
12015       case NormalMove:
12016       case FirstLeg:
12017       case WhiteKingSideCastle:
12018       case WhiteQueenSideCastle:
12019       case BlackKingSideCastle:
12020       case BlackQueenSideCastle:
12021       case WhiteKingSideCastleWild:
12022       case WhiteQueenSideCastleWild:
12023       case BlackKingSideCastleWild:
12024       case BlackQueenSideCastleWild:
12025       /* PUSH Fabien */
12026       case WhiteHSideCastleFR:
12027       case WhiteASideCastleFR:
12028       case BlackHSideCastleFR:
12029       case BlackASideCastleFR:
12030       /* POP Fabien */
12031         if (appData.debugMode)
12032           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12033         fromX = currentMoveString[0] - AAA;
12034         fromY = currentMoveString[1] - ONE;
12035         toX = currentMoveString[2] - AAA;
12036         toY = currentMoveString[3] - ONE;
12037         promoChar = currentMoveString[4];
12038         if(promoChar == ';') promoChar = NULLCHAR;
12039         break;
12040
12041       case WhiteDrop:
12042       case BlackDrop:
12043         if (appData.debugMode)
12044           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12045         fromX = moveType == WhiteDrop ?
12046           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12047         (int) CharToPiece(ToLower(currentMoveString[0]));
12048         fromY = DROP_RANK;
12049         toX = currentMoveString[2] - AAA;
12050         toY = currentMoveString[3] - ONE;
12051         break;
12052
12053       case WhiteWins:
12054       case BlackWins:
12055       case GameIsDrawn:
12056       case GameUnfinished:
12057         if (appData.debugMode)
12058           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12059         p = strchr(yy_text, '{');
12060         if (p == NULL) p = strchr(yy_text, '(');
12061         if (p == NULL) {
12062             p = yy_text;
12063             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12064         } else {
12065             q = strchr(p, *p == '{' ? '}' : ')');
12066             if (q != NULL) *q = NULLCHAR;
12067             p++;
12068         }
12069         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12070         GameEnds(moveType, p, GE_FILE);
12071         done = TRUE;
12072         if (cmailMsgLoaded) {
12073             ClearHighlights();
12074             flipView = WhiteOnMove(currentMove);
12075             if (moveType == GameUnfinished) flipView = !flipView;
12076             if (appData.debugMode)
12077               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12078         }
12079         break;
12080
12081       case EndOfFile:
12082         if (appData.debugMode)
12083           fprintf(debugFP, "Parser hit end of file\n");
12084         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12085           case MT_NONE:
12086           case MT_CHECK:
12087             break;
12088           case MT_CHECKMATE:
12089           case MT_STAINMATE:
12090             if (WhiteOnMove(currentMove)) {
12091                 GameEnds(BlackWins, "Black mates", GE_FILE);
12092             } else {
12093                 GameEnds(WhiteWins, "White mates", GE_FILE);
12094             }
12095             break;
12096           case MT_STALEMATE:
12097             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12098             break;
12099         }
12100         done = TRUE;
12101         break;
12102
12103       case MoveNumberOne:
12104         if (lastLoadGameStart == GNUChessGame) {
12105             /* GNUChessGames have numbers, but they aren't move numbers */
12106             if (appData.debugMode)
12107               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12108                       yy_text, (int) moveType);
12109             return LoadGameOneMove(EndOfFile); /* tail recursion */
12110         }
12111         /* else fall thru */
12112
12113       case XBoardGame:
12114       case GNUChessGame:
12115       case PGNTag:
12116         /* Reached start of next game in file */
12117         if (appData.debugMode)
12118           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12119         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12120           case MT_NONE:
12121           case MT_CHECK:
12122             break;
12123           case MT_CHECKMATE:
12124           case MT_STAINMATE:
12125             if (WhiteOnMove(currentMove)) {
12126                 GameEnds(BlackWins, "Black mates", GE_FILE);
12127             } else {
12128                 GameEnds(WhiteWins, "White mates", GE_FILE);
12129             }
12130             break;
12131           case MT_STALEMATE:
12132             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12133             break;
12134         }
12135         done = TRUE;
12136         break;
12137
12138       case PositionDiagram:     /* should not happen; ignore */
12139       case ElapsedTime:         /* ignore */
12140       case NAG:                 /* ignore */
12141         if (appData.debugMode)
12142           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12143                   yy_text, (int) moveType);
12144         return LoadGameOneMove(EndOfFile); /* tail recursion */
12145
12146       case IllegalMove:
12147         if (appData.testLegality) {
12148             if (appData.debugMode)
12149               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12150             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12151                     (forwardMostMove / 2) + 1,
12152                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12153             DisplayError(move, 0);
12154             done = TRUE;
12155         } else {
12156             if (appData.debugMode)
12157               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12158                       yy_text, currentMoveString);
12159             if(currentMoveString[1] == '@') {
12160                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12161                 fromY = DROP_RANK;
12162             } else {
12163                 fromX = currentMoveString[0] - AAA;
12164                 fromY = currentMoveString[1] - ONE;
12165             }
12166             toX = currentMoveString[2] - AAA;
12167             toY = currentMoveString[3] - ONE;
12168             promoChar = currentMoveString[4];
12169         }
12170         break;
12171
12172       case AmbiguousMove:
12173         if (appData.debugMode)
12174           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12175         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12176                 (forwardMostMove / 2) + 1,
12177                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12178         DisplayError(move, 0);
12179         done = TRUE;
12180         break;
12181
12182       default:
12183       case ImpossibleMove:
12184         if (appData.debugMode)
12185           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12186         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12187                 (forwardMostMove / 2) + 1,
12188                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12189         DisplayError(move, 0);
12190         done = TRUE;
12191         break;
12192     }
12193
12194     if (done) {
12195         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12196             DrawPosition(FALSE, boards[currentMove]);
12197             DisplayBothClocks();
12198             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12199               DisplayComment(currentMove - 1, commentList[currentMove]);
12200         }
12201         (void) StopLoadGameTimer();
12202         gameFileFP = NULL;
12203         cmailOldMove = forwardMostMove;
12204         return FALSE;
12205     } else {
12206         /* currentMoveString is set as a side-effect of yylex */
12207
12208         thinkOutput[0] = NULLCHAR;
12209         MakeMove(fromX, fromY, toX, toY, promoChar);
12210         killX = killY = -1; // [HGM] lion: used up
12211         currentMove = forwardMostMove;
12212         return TRUE;
12213     }
12214 }
12215
12216 /* Load the nth game from the given file */
12217 int
12218 LoadGameFromFile (char *filename, int n, char *title, int useList)
12219 {
12220     FILE *f;
12221     char buf[MSG_SIZ];
12222
12223     if (strcmp(filename, "-") == 0) {
12224         f = stdin;
12225         title = "stdin";
12226     } else {
12227         f = fopen(filename, "rb");
12228         if (f == NULL) {
12229           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12230             DisplayError(buf, errno);
12231             return FALSE;
12232         }
12233     }
12234     if (fseek(f, 0, 0) == -1) {
12235         /* f is not seekable; probably a pipe */
12236         useList = FALSE;
12237     }
12238     if (useList && n == 0) {
12239         int error = GameListBuild(f);
12240         if (error) {
12241             DisplayError(_("Cannot build game list"), error);
12242         } else if (!ListEmpty(&gameList) &&
12243                    ((ListGame *) gameList.tailPred)->number > 1) {
12244             GameListPopUp(f, title);
12245             return TRUE;
12246         }
12247         GameListDestroy();
12248         n = 1;
12249     }
12250     if (n == 0) n = 1;
12251     return LoadGame(f, n, title, FALSE);
12252 }
12253
12254
12255 void
12256 MakeRegisteredMove ()
12257 {
12258     int fromX, fromY, toX, toY;
12259     char promoChar;
12260     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12261         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12262           case CMAIL_MOVE:
12263           case CMAIL_DRAW:
12264             if (appData.debugMode)
12265               fprintf(debugFP, "Restoring %s for game %d\n",
12266                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12267
12268             thinkOutput[0] = NULLCHAR;
12269             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12270             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12271             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12272             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12273             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12274             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12275             MakeMove(fromX, fromY, toX, toY, promoChar);
12276             ShowMove(fromX, fromY, toX, toY);
12277
12278             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12279               case MT_NONE:
12280               case MT_CHECK:
12281                 break;
12282
12283               case MT_CHECKMATE:
12284               case MT_STAINMATE:
12285                 if (WhiteOnMove(currentMove)) {
12286                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12287                 } else {
12288                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12289                 }
12290                 break;
12291
12292               case MT_STALEMATE:
12293                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12294                 break;
12295             }
12296
12297             break;
12298
12299           case CMAIL_RESIGN:
12300             if (WhiteOnMove(currentMove)) {
12301                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12302             } else {
12303                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12304             }
12305             break;
12306
12307           case CMAIL_ACCEPT:
12308             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12309             break;
12310
12311           default:
12312             break;
12313         }
12314     }
12315
12316     return;
12317 }
12318
12319 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12320 int
12321 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12322 {
12323     int retVal;
12324
12325     if (gameNumber > nCmailGames) {
12326         DisplayError(_("No more games in this message"), 0);
12327         return FALSE;
12328     }
12329     if (f == lastLoadGameFP) {
12330         int offset = gameNumber - lastLoadGameNumber;
12331         if (offset == 0) {
12332             cmailMsg[0] = NULLCHAR;
12333             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12334                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12335                 nCmailMovesRegistered--;
12336             }
12337             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12338             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12339                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12340             }
12341         } else {
12342             if (! RegisterMove()) return FALSE;
12343         }
12344     }
12345
12346     retVal = LoadGame(f, gameNumber, title, useList);
12347
12348     /* Make move registered during previous look at this game, if any */
12349     MakeRegisteredMove();
12350
12351     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12352         commentList[currentMove]
12353           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12354         DisplayComment(currentMove - 1, commentList[currentMove]);
12355     }
12356
12357     return retVal;
12358 }
12359
12360 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12361 int
12362 ReloadGame (int offset)
12363 {
12364     int gameNumber = lastLoadGameNumber + offset;
12365     if (lastLoadGameFP == NULL) {
12366         DisplayError(_("No game has been loaded yet"), 0);
12367         return FALSE;
12368     }
12369     if (gameNumber <= 0) {
12370         DisplayError(_("Can't back up any further"), 0);
12371         return FALSE;
12372     }
12373     if (cmailMsgLoaded) {
12374         return CmailLoadGame(lastLoadGameFP, gameNumber,
12375                              lastLoadGameTitle, lastLoadGameUseList);
12376     } else {
12377         return LoadGame(lastLoadGameFP, gameNumber,
12378                         lastLoadGameTitle, lastLoadGameUseList);
12379     }
12380 }
12381
12382 int keys[EmptySquare+1];
12383
12384 int
12385 PositionMatches (Board b1, Board b2)
12386 {
12387     int r, f, sum=0;
12388     switch(appData.searchMode) {
12389         case 1: return CompareWithRights(b1, b2);
12390         case 2:
12391             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12392                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12393             }
12394             return TRUE;
12395         case 3:
12396             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12397               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12398                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12399             }
12400             return sum==0;
12401         case 4:
12402             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12403                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12404             }
12405             return sum==0;
12406     }
12407     return TRUE;
12408 }
12409
12410 #define Q_PROMO  4
12411 #define Q_EP     3
12412 #define Q_BCASTL 2
12413 #define Q_WCASTL 1
12414
12415 int pieceList[256], quickBoard[256];
12416 ChessSquare pieceType[256] = { EmptySquare };
12417 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12418 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12419 int soughtTotal, turn;
12420 Boolean epOK, flipSearch;
12421
12422 typedef struct {
12423     unsigned char piece, to;
12424 } Move;
12425
12426 #define DSIZE (250000)
12427
12428 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12429 Move *moveDatabase = initialSpace;
12430 unsigned int movePtr, dataSize = DSIZE;
12431
12432 int
12433 MakePieceList (Board board, int *counts)
12434 {
12435     int r, f, n=Q_PROMO, total=0;
12436     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12437     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12438         int sq = f + (r<<4);
12439         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12440             quickBoard[sq] = ++n;
12441             pieceList[n] = sq;
12442             pieceType[n] = board[r][f];
12443             counts[board[r][f]]++;
12444             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12445             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12446             total++;
12447         }
12448     }
12449     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12450     return total;
12451 }
12452
12453 void
12454 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12455 {
12456     int sq = fromX + (fromY<<4);
12457     int piece = quickBoard[sq], rook;
12458     quickBoard[sq] = 0;
12459     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12460     if(piece == pieceList[1] && fromY == toY) {
12461       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12462         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12463         moveDatabase[movePtr++].piece = Q_WCASTL;
12464         quickBoard[sq] = piece;
12465         piece = quickBoard[from]; quickBoard[from] = 0;
12466         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12467       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12468         quickBoard[sq] = 0; // remove Rook
12469         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12470         moveDatabase[movePtr++].piece = Q_WCASTL;
12471         quickBoard[sq] = pieceList[1]; // put King
12472         piece = rook;
12473         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12474       }
12475     } else
12476     if(piece == pieceList[2] && fromY == toY) {
12477       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12478         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12479         moveDatabase[movePtr++].piece = Q_BCASTL;
12480         quickBoard[sq] = piece;
12481         piece = quickBoard[from]; quickBoard[from] = 0;
12482         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12483       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12484         quickBoard[sq] = 0; // remove Rook
12485         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12486         moveDatabase[movePtr++].piece = Q_BCASTL;
12487         quickBoard[sq] = pieceList[2]; // put King
12488         piece = rook;
12489         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12490       }
12491     } else
12492     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12493         quickBoard[(fromY<<4)+toX] = 0;
12494         moveDatabase[movePtr].piece = Q_EP;
12495         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12496         moveDatabase[movePtr].to = sq;
12497     } else
12498     if(promoPiece != pieceType[piece]) {
12499         moveDatabase[movePtr++].piece = Q_PROMO;
12500         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12501     }
12502     moveDatabase[movePtr].piece = piece;
12503     quickBoard[sq] = piece;
12504     movePtr++;
12505 }
12506
12507 int
12508 PackGame (Board board)
12509 {
12510     Move *newSpace = NULL;
12511     moveDatabase[movePtr].piece = 0; // terminate previous game
12512     if(movePtr > dataSize) {
12513         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12514         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12515         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12516         if(newSpace) {
12517             int i;
12518             Move *p = moveDatabase, *q = newSpace;
12519             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12520             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12521             moveDatabase = newSpace;
12522         } else { // calloc failed, we must be out of memory. Too bad...
12523             dataSize = 0; // prevent calloc events for all subsequent games
12524             return 0;     // and signal this one isn't cached
12525         }
12526     }
12527     movePtr++;
12528     MakePieceList(board, counts);
12529     return movePtr;
12530 }
12531
12532 int
12533 QuickCompare (Board board, int *minCounts, int *maxCounts)
12534 {   // compare according to search mode
12535     int r, f;
12536     switch(appData.searchMode)
12537     {
12538       case 1: // exact position match
12539         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12540         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12541             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12542         }
12543         break;
12544       case 2: // can have extra material on empty squares
12545         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12546             if(board[r][f] == EmptySquare) continue;
12547             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12548         }
12549         break;
12550       case 3: // material with exact Pawn structure
12551         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12552             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12553             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12554         } // fall through to material comparison
12555       case 4: // exact material
12556         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12557         break;
12558       case 6: // material range with given imbalance
12559         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12560         // fall through to range comparison
12561       case 5: // material range
12562         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12563     }
12564     return TRUE;
12565 }
12566
12567 int
12568 QuickScan (Board board, Move *move)
12569 {   // reconstruct game,and compare all positions in it
12570     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12571     do {
12572         int piece = move->piece;
12573         int to = move->to, from = pieceList[piece];
12574         if(found < 0) { // if already found just scan to game end for final piece count
12575           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12576            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12577            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12578                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12579             ) {
12580             static int lastCounts[EmptySquare+1];
12581             int i;
12582             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12583             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12584           } else stretch = 0;
12585           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12586           if(found >= 0 && !appData.minPieces) return found;
12587         }
12588         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12589           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12590           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12591             piece = (++move)->piece;
12592             from = pieceList[piece];
12593             counts[pieceType[piece]]--;
12594             pieceType[piece] = (ChessSquare) move->to;
12595             counts[move->to]++;
12596           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12597             counts[pieceType[quickBoard[to]]]--;
12598             quickBoard[to] = 0; total--;
12599             move++;
12600             continue;
12601           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12602             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12603             from  = pieceList[piece]; // so this must be King
12604             quickBoard[from] = 0;
12605             pieceList[piece] = to;
12606             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12607             quickBoard[from] = 0; // rook
12608             quickBoard[to] = piece;
12609             to = move->to; piece = move->piece;
12610             goto aftercastle;
12611           }
12612         }
12613         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12614         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12615         quickBoard[from] = 0;
12616       aftercastle:
12617         quickBoard[to] = piece;
12618         pieceList[piece] = to;
12619         cnt++; turn ^= 3;
12620         move++;
12621     } while(1);
12622 }
12623
12624 void
12625 InitSearch ()
12626 {
12627     int r, f;
12628     flipSearch = FALSE;
12629     CopyBoard(soughtBoard, boards[currentMove]);
12630     soughtTotal = MakePieceList(soughtBoard, maxSought);
12631     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12632     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12633     CopyBoard(reverseBoard, boards[currentMove]);
12634     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12635         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12636         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12637         reverseBoard[r][f] = piece;
12638     }
12639     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12640     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12641     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12642                  || (boards[currentMove][CASTLING][2] == NoRights ||
12643                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12644                  && (boards[currentMove][CASTLING][5] == NoRights ||
12645                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12646       ) {
12647         flipSearch = TRUE;
12648         CopyBoard(flipBoard, soughtBoard);
12649         CopyBoard(rotateBoard, reverseBoard);
12650         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12651             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12652             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12653         }
12654     }
12655     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12656     if(appData.searchMode >= 5) {
12657         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12658         MakePieceList(soughtBoard, minSought);
12659         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12660     }
12661     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12662         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12663 }
12664
12665 GameInfo dummyInfo;
12666 static int creatingBook;
12667
12668 int
12669 GameContainsPosition (FILE *f, ListGame *lg)
12670 {
12671     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12672     int fromX, fromY, toX, toY;
12673     char promoChar;
12674     static int initDone=FALSE;
12675
12676     // weed out games based on numerical tag comparison
12677     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12678     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12679     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12680     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12681     if(!initDone) {
12682         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12683         initDone = TRUE;
12684     }
12685     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12686     else CopyBoard(boards[scratch], initialPosition); // default start position
12687     if(lg->moves) {
12688         turn = btm + 1;
12689         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12690         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12691     }
12692     if(btm) plyNr++;
12693     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12694     fseek(f, lg->offset, 0);
12695     yynewfile(f);
12696     while(1) {
12697         yyboardindex = scratch;
12698         quickFlag = plyNr+1;
12699         next = Myylex();
12700         quickFlag = 0;
12701         switch(next) {
12702             case PGNTag:
12703                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12704             default:
12705                 continue;
12706
12707             case XBoardGame:
12708             case GNUChessGame:
12709                 if(plyNr) return -1; // after we have seen moves, this is for new game
12710               continue;
12711
12712             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12713             case ImpossibleMove:
12714             case WhiteWins: // game ends here with these four
12715             case BlackWins:
12716             case GameIsDrawn:
12717             case GameUnfinished:
12718                 return -1;
12719
12720             case IllegalMove:
12721                 if(appData.testLegality) return -1;
12722             case WhiteCapturesEnPassant:
12723             case BlackCapturesEnPassant:
12724             case WhitePromotion:
12725             case BlackPromotion:
12726             case WhiteNonPromotion:
12727             case BlackNonPromotion:
12728             case NormalMove:
12729             case FirstLeg:
12730             case WhiteKingSideCastle:
12731             case WhiteQueenSideCastle:
12732             case BlackKingSideCastle:
12733             case BlackQueenSideCastle:
12734             case WhiteKingSideCastleWild:
12735             case WhiteQueenSideCastleWild:
12736             case BlackKingSideCastleWild:
12737             case BlackQueenSideCastleWild:
12738             case WhiteHSideCastleFR:
12739             case WhiteASideCastleFR:
12740             case BlackHSideCastleFR:
12741             case BlackASideCastleFR:
12742                 fromX = currentMoveString[0] - AAA;
12743                 fromY = currentMoveString[1] - ONE;
12744                 toX = currentMoveString[2] - AAA;
12745                 toY = currentMoveString[3] - ONE;
12746                 promoChar = currentMoveString[4];
12747                 break;
12748             case WhiteDrop:
12749             case BlackDrop:
12750                 fromX = next == WhiteDrop ?
12751                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12752                   (int) CharToPiece(ToLower(currentMoveString[0]));
12753                 fromY = DROP_RANK;
12754                 toX = currentMoveString[2] - AAA;
12755                 toY = currentMoveString[3] - ONE;
12756                 promoChar = 0;
12757                 break;
12758         }
12759         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12760         plyNr++;
12761         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12762         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12763         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12764         if(appData.findMirror) {
12765             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12766             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12767         }
12768     }
12769 }
12770
12771 /* Load the nth game from open file f */
12772 int
12773 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12774 {
12775     ChessMove cm;
12776     char buf[MSG_SIZ];
12777     int gn = gameNumber;
12778     ListGame *lg = NULL;
12779     int numPGNTags = 0;
12780     int err, pos = -1;
12781     GameMode oldGameMode;
12782     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12783     char oldName[MSG_SIZ];
12784
12785     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12786
12787     if (appData.debugMode)
12788         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12789
12790     if (gameMode == Training )
12791         SetTrainingModeOff();
12792
12793     oldGameMode = gameMode;
12794     if (gameMode != BeginningOfGame) {
12795       Reset(FALSE, TRUE);
12796     }
12797     killX = killY = -1; // [HGM] lion: in case we did not Reset
12798
12799     gameFileFP = f;
12800     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12801         fclose(lastLoadGameFP);
12802     }
12803
12804     if (useList) {
12805         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12806
12807         if (lg) {
12808             fseek(f, lg->offset, 0);
12809             GameListHighlight(gameNumber);
12810             pos = lg->position;
12811             gn = 1;
12812         }
12813         else {
12814             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12815               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12816             else
12817             DisplayError(_("Game number out of range"), 0);
12818             return FALSE;
12819         }
12820     } else {
12821         GameListDestroy();
12822         if (fseek(f, 0, 0) == -1) {
12823             if (f == lastLoadGameFP ?
12824                 gameNumber == lastLoadGameNumber + 1 :
12825                 gameNumber == 1) {
12826                 gn = 1;
12827             } else {
12828                 DisplayError(_("Can't seek on game file"), 0);
12829                 return FALSE;
12830             }
12831         }
12832     }
12833     lastLoadGameFP = f;
12834     lastLoadGameNumber = gameNumber;
12835     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12836     lastLoadGameUseList = useList;
12837
12838     yynewfile(f);
12839
12840     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12841       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12842                 lg->gameInfo.black);
12843             DisplayTitle(buf);
12844     } else if (*title != NULLCHAR) {
12845         if (gameNumber > 1) {
12846           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12847             DisplayTitle(buf);
12848         } else {
12849             DisplayTitle(title);
12850         }
12851     }
12852
12853     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12854         gameMode = PlayFromGameFile;
12855         ModeHighlight();
12856     }
12857
12858     currentMove = forwardMostMove = backwardMostMove = 0;
12859     CopyBoard(boards[0], initialPosition);
12860     StopClocks();
12861
12862     /*
12863      * Skip the first gn-1 games in the file.
12864      * Also skip over anything that precedes an identifiable
12865      * start of game marker, to avoid being confused by
12866      * garbage at the start of the file.  Currently
12867      * recognized start of game markers are the move number "1",
12868      * the pattern "gnuchess .* game", the pattern
12869      * "^[#;%] [^ ]* game file", and a PGN tag block.
12870      * A game that starts with one of the latter two patterns
12871      * will also have a move number 1, possibly
12872      * following a position diagram.
12873      * 5-4-02: Let's try being more lenient and allowing a game to
12874      * start with an unnumbered move.  Does that break anything?
12875      */
12876     cm = lastLoadGameStart = EndOfFile;
12877     while (gn > 0) {
12878         yyboardindex = forwardMostMove;
12879         cm = (ChessMove) Myylex();
12880         switch (cm) {
12881           case EndOfFile:
12882             if (cmailMsgLoaded) {
12883                 nCmailGames = CMAIL_MAX_GAMES - gn;
12884             } else {
12885                 Reset(TRUE, TRUE);
12886                 DisplayError(_("Game not found in file"), 0);
12887             }
12888             return FALSE;
12889
12890           case GNUChessGame:
12891           case XBoardGame:
12892             gn--;
12893             lastLoadGameStart = cm;
12894             break;
12895
12896           case MoveNumberOne:
12897             switch (lastLoadGameStart) {
12898               case GNUChessGame:
12899               case XBoardGame:
12900               case PGNTag:
12901                 break;
12902               case MoveNumberOne:
12903               case EndOfFile:
12904                 gn--;           /* count this game */
12905                 lastLoadGameStart = cm;
12906                 break;
12907               default:
12908                 /* impossible */
12909                 break;
12910             }
12911             break;
12912
12913           case PGNTag:
12914             switch (lastLoadGameStart) {
12915               case GNUChessGame:
12916               case PGNTag:
12917               case MoveNumberOne:
12918               case EndOfFile:
12919                 gn--;           /* count this game */
12920                 lastLoadGameStart = cm;
12921                 break;
12922               case XBoardGame:
12923                 lastLoadGameStart = cm; /* game counted already */
12924                 break;
12925               default:
12926                 /* impossible */
12927                 break;
12928             }
12929             if (gn > 0) {
12930                 do {
12931                     yyboardindex = forwardMostMove;
12932                     cm = (ChessMove) Myylex();
12933                 } while (cm == PGNTag || cm == Comment);
12934             }
12935             break;
12936
12937           case WhiteWins:
12938           case BlackWins:
12939           case GameIsDrawn:
12940             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12941                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12942                     != CMAIL_OLD_RESULT) {
12943                     nCmailResults ++ ;
12944                     cmailResult[  CMAIL_MAX_GAMES
12945                                 - gn - 1] = CMAIL_OLD_RESULT;
12946                 }
12947             }
12948             break;
12949
12950           case NormalMove:
12951           case FirstLeg:
12952             /* Only a NormalMove can be at the start of a game
12953              * without a position diagram. */
12954             if (lastLoadGameStart == EndOfFile ) {
12955               gn--;
12956               lastLoadGameStart = MoveNumberOne;
12957             }
12958             break;
12959
12960           default:
12961             break;
12962         }
12963     }
12964
12965     if (appData.debugMode)
12966       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12967
12968     if (cm == XBoardGame) {
12969         /* Skip any header junk before position diagram and/or move 1 */
12970         for (;;) {
12971             yyboardindex = forwardMostMove;
12972             cm = (ChessMove) Myylex();
12973
12974             if (cm == EndOfFile ||
12975                 cm == GNUChessGame || cm == XBoardGame) {
12976                 /* Empty game; pretend end-of-file and handle later */
12977                 cm = EndOfFile;
12978                 break;
12979             }
12980
12981             if (cm == MoveNumberOne || cm == PositionDiagram ||
12982                 cm == PGNTag || cm == Comment)
12983               break;
12984         }
12985     } else if (cm == GNUChessGame) {
12986         if (gameInfo.event != NULL) {
12987             free(gameInfo.event);
12988         }
12989         gameInfo.event = StrSave(yy_text);
12990     }
12991
12992     startedFromSetupPosition = FALSE;
12993     while (cm == PGNTag) {
12994         if (appData.debugMode)
12995           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12996         err = ParsePGNTag(yy_text, &gameInfo);
12997         if (!err) numPGNTags++;
12998
12999         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13000         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13001             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13002             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13003             InitPosition(TRUE);
13004             oldVariant = gameInfo.variant;
13005             if (appData.debugMode)
13006               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13007         }
13008
13009
13010         if (gameInfo.fen != NULL) {
13011           Board initial_position;
13012           startedFromSetupPosition = TRUE;
13013           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13014             Reset(TRUE, TRUE);
13015             DisplayError(_("Bad FEN position in file"), 0);
13016             return FALSE;
13017           }
13018           CopyBoard(boards[0], initial_position);
13019           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13020             CopyBoard(initialPosition, initial_position);
13021           if (blackPlaysFirst) {
13022             currentMove = forwardMostMove = backwardMostMove = 1;
13023             CopyBoard(boards[1], initial_position);
13024             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13025             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13026             timeRemaining[0][1] = whiteTimeRemaining;
13027             timeRemaining[1][1] = blackTimeRemaining;
13028             if (commentList[0] != NULL) {
13029               commentList[1] = commentList[0];
13030               commentList[0] = NULL;
13031             }
13032           } else {
13033             currentMove = forwardMostMove = backwardMostMove = 0;
13034           }
13035           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13036           {   int i;
13037               initialRulePlies = FENrulePlies;
13038               for( i=0; i< nrCastlingRights; i++ )
13039                   initialRights[i] = initial_position[CASTLING][i];
13040           }
13041           yyboardindex = forwardMostMove;
13042           free(gameInfo.fen);
13043           gameInfo.fen = NULL;
13044         }
13045
13046         yyboardindex = forwardMostMove;
13047         cm = (ChessMove) Myylex();
13048
13049         /* Handle comments interspersed among the tags */
13050         while (cm == Comment) {
13051             char *p;
13052             if (appData.debugMode)
13053               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13054             p = yy_text;
13055             AppendComment(currentMove, p, FALSE);
13056             yyboardindex = forwardMostMove;
13057             cm = (ChessMove) Myylex();
13058         }
13059     }
13060
13061     /* don't rely on existence of Event tag since if game was
13062      * pasted from clipboard the Event tag may not exist
13063      */
13064     if (numPGNTags > 0){
13065         char *tags;
13066         if (gameInfo.variant == VariantNormal) {
13067           VariantClass v = StringToVariant(gameInfo.event);
13068           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13069           if(v < VariantShogi) gameInfo.variant = v;
13070         }
13071         if (!matchMode) {
13072           if( appData.autoDisplayTags ) {
13073             tags = PGNTags(&gameInfo);
13074             TagsPopUp(tags, CmailMsg());
13075             free(tags);
13076           }
13077         }
13078     } else {
13079         /* Make something up, but don't display it now */
13080         SetGameInfo();
13081         TagsPopDown();
13082     }
13083
13084     if (cm == PositionDiagram) {
13085         int i, j;
13086         char *p;
13087         Board initial_position;
13088
13089         if (appData.debugMode)
13090           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13091
13092         if (!startedFromSetupPosition) {
13093             p = yy_text;
13094             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13095               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13096                 switch (*p) {
13097                   case '{':
13098                   case '[':
13099                   case '-':
13100                   case ' ':
13101                   case '\t':
13102                   case '\n':
13103                   case '\r':
13104                     break;
13105                   default:
13106                     initial_position[i][j++] = CharToPiece(*p);
13107                     break;
13108                 }
13109             while (*p == ' ' || *p == '\t' ||
13110                    *p == '\n' || *p == '\r') p++;
13111
13112             if (strncmp(p, "black", strlen("black"))==0)
13113               blackPlaysFirst = TRUE;
13114             else
13115               blackPlaysFirst = FALSE;
13116             startedFromSetupPosition = TRUE;
13117
13118             CopyBoard(boards[0], initial_position);
13119             if (blackPlaysFirst) {
13120                 currentMove = forwardMostMove = backwardMostMove = 1;
13121                 CopyBoard(boards[1], initial_position);
13122                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13123                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13124                 timeRemaining[0][1] = whiteTimeRemaining;
13125                 timeRemaining[1][1] = blackTimeRemaining;
13126                 if (commentList[0] != NULL) {
13127                     commentList[1] = commentList[0];
13128                     commentList[0] = NULL;
13129                 }
13130             } else {
13131                 currentMove = forwardMostMove = backwardMostMove = 0;
13132             }
13133         }
13134         yyboardindex = forwardMostMove;
13135         cm = (ChessMove) Myylex();
13136     }
13137
13138   if(!creatingBook) {
13139     if (first.pr == NoProc) {
13140         StartChessProgram(&first);
13141     }
13142     InitChessProgram(&first, FALSE);
13143     if(gameInfo.variant == VariantUnknown && *oldName) {
13144         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13145         gameInfo.variant = v;
13146     }
13147     SendToProgram("force\n", &first);
13148     if (startedFromSetupPosition) {
13149         SendBoard(&first, forwardMostMove);
13150     if (appData.debugMode) {
13151         fprintf(debugFP, "Load Game\n");
13152     }
13153         DisplayBothClocks();
13154     }
13155   }
13156
13157     /* [HGM] server: flag to write setup moves in broadcast file as one */
13158     loadFlag = appData.suppressLoadMoves;
13159
13160     while (cm == Comment) {
13161         char *p;
13162         if (appData.debugMode)
13163           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13164         p = yy_text;
13165         AppendComment(currentMove, p, FALSE);
13166         yyboardindex = forwardMostMove;
13167         cm = (ChessMove) Myylex();
13168     }
13169
13170     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13171         cm == WhiteWins || cm == BlackWins ||
13172         cm == GameIsDrawn || cm == GameUnfinished) {
13173         DisplayMessage("", _("No moves in game"));
13174         if (cmailMsgLoaded) {
13175             if (appData.debugMode)
13176               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13177             ClearHighlights();
13178             flipView = FALSE;
13179         }
13180         DrawPosition(FALSE, boards[currentMove]);
13181         DisplayBothClocks();
13182         gameMode = EditGame;
13183         ModeHighlight();
13184         gameFileFP = NULL;
13185         cmailOldMove = 0;
13186         return TRUE;
13187     }
13188
13189     // [HGM] PV info: routine tests if comment empty
13190     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13191         DisplayComment(currentMove - 1, commentList[currentMove]);
13192     }
13193     if (!matchMode && appData.timeDelay != 0)
13194       DrawPosition(FALSE, boards[currentMove]);
13195
13196     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13197       programStats.ok_to_send = 1;
13198     }
13199
13200     /* if the first token after the PGN tags is a move
13201      * and not move number 1, retrieve it from the parser
13202      */
13203     if (cm != MoveNumberOne)
13204         LoadGameOneMove(cm);
13205
13206     /* load the remaining moves from the file */
13207     while (LoadGameOneMove(EndOfFile)) {
13208       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13209       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13210     }
13211
13212     /* rewind to the start of the game */
13213     currentMove = backwardMostMove;
13214
13215     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13216
13217     if (oldGameMode == AnalyzeFile) {
13218       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13219       AnalyzeFileEvent();
13220     } else
13221     if (oldGameMode == AnalyzeMode) {
13222       AnalyzeFileEvent();
13223     }
13224
13225     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13226         long int w, b; // [HGM] adjourn: restore saved clock times
13227         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13228         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13229             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13230             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13231         }
13232     }
13233
13234     if(creatingBook) return TRUE;
13235     if (!matchMode && pos > 0) {
13236         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13237     } else
13238     if (matchMode || appData.timeDelay == 0) {
13239       ToEndEvent();
13240     } else if (appData.timeDelay > 0) {
13241       AutoPlayGameLoop();
13242     }
13243
13244     if (appData.debugMode)
13245         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13246
13247     loadFlag = 0; /* [HGM] true game starts */
13248     return TRUE;
13249 }
13250
13251 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13252 int
13253 ReloadPosition (int offset)
13254 {
13255     int positionNumber = lastLoadPositionNumber + offset;
13256     if (lastLoadPositionFP == NULL) {
13257         DisplayError(_("No position has been loaded yet"), 0);
13258         return FALSE;
13259     }
13260     if (positionNumber <= 0) {
13261         DisplayError(_("Can't back up any further"), 0);
13262         return FALSE;
13263     }
13264     return LoadPosition(lastLoadPositionFP, positionNumber,
13265                         lastLoadPositionTitle);
13266 }
13267
13268 /* Load the nth position from the given file */
13269 int
13270 LoadPositionFromFile (char *filename, int n, char *title)
13271 {
13272     FILE *f;
13273     char buf[MSG_SIZ];
13274
13275     if (strcmp(filename, "-") == 0) {
13276         return LoadPosition(stdin, n, "stdin");
13277     } else {
13278         f = fopen(filename, "rb");
13279         if (f == NULL) {
13280             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13281             DisplayError(buf, errno);
13282             return FALSE;
13283         } else {
13284             return LoadPosition(f, n, title);
13285         }
13286     }
13287 }
13288
13289 /* Load the nth position from the given open file, and close it */
13290 int
13291 LoadPosition (FILE *f, int positionNumber, char *title)
13292 {
13293     char *p, line[MSG_SIZ];
13294     Board initial_position;
13295     int i, j, fenMode, pn;
13296
13297     if (gameMode == Training )
13298         SetTrainingModeOff();
13299
13300     if (gameMode != BeginningOfGame) {
13301         Reset(FALSE, TRUE);
13302     }
13303     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13304         fclose(lastLoadPositionFP);
13305     }
13306     if (positionNumber == 0) positionNumber = 1;
13307     lastLoadPositionFP = f;
13308     lastLoadPositionNumber = positionNumber;
13309     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13310     if (first.pr == NoProc && !appData.noChessProgram) {
13311       StartChessProgram(&first);
13312       InitChessProgram(&first, FALSE);
13313     }
13314     pn = positionNumber;
13315     if (positionNumber < 0) {
13316         /* Negative position number means to seek to that byte offset */
13317         if (fseek(f, -positionNumber, 0) == -1) {
13318             DisplayError(_("Can't seek on position file"), 0);
13319             return FALSE;
13320         };
13321         pn = 1;
13322     } else {
13323         if (fseek(f, 0, 0) == -1) {
13324             if (f == lastLoadPositionFP ?
13325                 positionNumber == lastLoadPositionNumber + 1 :
13326                 positionNumber == 1) {
13327                 pn = 1;
13328             } else {
13329                 DisplayError(_("Can't seek on position file"), 0);
13330                 return FALSE;
13331             }
13332         }
13333     }
13334     /* See if this file is FEN or old-style xboard */
13335     if (fgets(line, MSG_SIZ, f) == NULL) {
13336         DisplayError(_("Position not found in file"), 0);
13337         return FALSE;
13338     }
13339     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13340     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13341
13342     if (pn >= 2) {
13343         if (fenMode || line[0] == '#') pn--;
13344         while (pn > 0) {
13345             /* skip positions before number pn */
13346             if (fgets(line, MSG_SIZ, f) == NULL) {
13347                 Reset(TRUE, TRUE);
13348                 DisplayError(_("Position not found in file"), 0);
13349                 return FALSE;
13350             }
13351             if (fenMode || line[0] == '#') pn--;
13352         }
13353     }
13354
13355     if (fenMode) {
13356         char *p;
13357         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13358             DisplayError(_("Bad FEN position in file"), 0);
13359             return FALSE;
13360         }
13361         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13362             sscanf(p+3, "%s", bestMove);
13363         } else *bestMove = NULLCHAR;
13364     } else {
13365         (void) fgets(line, MSG_SIZ, f);
13366         (void) fgets(line, MSG_SIZ, f);
13367
13368         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13369             (void) fgets(line, MSG_SIZ, f);
13370             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13371                 if (*p == ' ')
13372                   continue;
13373                 initial_position[i][j++] = CharToPiece(*p);
13374             }
13375         }
13376
13377         blackPlaysFirst = FALSE;
13378         if (!feof(f)) {
13379             (void) fgets(line, MSG_SIZ, f);
13380             if (strncmp(line, "black", strlen("black"))==0)
13381               blackPlaysFirst = TRUE;
13382         }
13383     }
13384     startedFromSetupPosition = TRUE;
13385
13386     CopyBoard(boards[0], initial_position);
13387     if (blackPlaysFirst) {
13388         currentMove = forwardMostMove = backwardMostMove = 1;
13389         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13390         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13391         CopyBoard(boards[1], initial_position);
13392         DisplayMessage("", _("Black to play"));
13393     } else {
13394         currentMove = forwardMostMove = backwardMostMove = 0;
13395         DisplayMessage("", _("White to play"));
13396     }
13397     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13398     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13399         SendToProgram("force\n", &first);
13400         SendBoard(&first, forwardMostMove);
13401     }
13402     if (appData.debugMode) {
13403 int i, j;
13404   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13405   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13406         fprintf(debugFP, "Load Position\n");
13407     }
13408
13409     if (positionNumber > 1) {
13410       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13411         DisplayTitle(line);
13412     } else {
13413         DisplayTitle(title);
13414     }
13415     gameMode = EditGame;
13416     ModeHighlight();
13417     ResetClocks();
13418     timeRemaining[0][1] = whiteTimeRemaining;
13419     timeRemaining[1][1] = blackTimeRemaining;
13420     DrawPosition(FALSE, boards[currentMove]);
13421
13422     return TRUE;
13423 }
13424
13425
13426 void
13427 CopyPlayerNameIntoFileName (char **dest, char *src)
13428 {
13429     while (*src != NULLCHAR && *src != ',') {
13430         if (*src == ' ') {
13431             *(*dest)++ = '_';
13432             src++;
13433         } else {
13434             *(*dest)++ = *src++;
13435         }
13436     }
13437 }
13438
13439 char *
13440 DefaultFileName (char *ext)
13441 {
13442     static char def[MSG_SIZ];
13443     char *p;
13444
13445     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13446         p = def;
13447         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13448         *p++ = '-';
13449         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13450         *p++ = '.';
13451         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13452     } else {
13453         def[0] = NULLCHAR;
13454     }
13455     return def;
13456 }
13457
13458 /* Save the current game to the given file */
13459 int
13460 SaveGameToFile (char *filename, int append)
13461 {
13462     FILE *f;
13463     char buf[MSG_SIZ];
13464     int result, i, t,tot=0;
13465
13466     if (strcmp(filename, "-") == 0) {
13467         return SaveGame(stdout, 0, NULL);
13468     } else {
13469         for(i=0; i<10; i++) { // upto 10 tries
13470              f = fopen(filename, append ? "a" : "w");
13471              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13472              if(f || errno != 13) break;
13473              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13474              tot += t;
13475         }
13476         if (f == NULL) {
13477             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13478             DisplayError(buf, errno);
13479             return FALSE;
13480         } else {
13481             safeStrCpy(buf, lastMsg, MSG_SIZ);
13482             DisplayMessage(_("Waiting for access to save file"), "");
13483             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13484             DisplayMessage(_("Saving game"), "");
13485             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13486             result = SaveGame(f, 0, NULL);
13487             DisplayMessage(buf, "");
13488             return result;
13489         }
13490     }
13491 }
13492
13493 char *
13494 SavePart (char *str)
13495 {
13496     static char buf[MSG_SIZ];
13497     char *p;
13498
13499     p = strchr(str, ' ');
13500     if (p == NULL) return str;
13501     strncpy(buf, str, p - str);
13502     buf[p - str] = NULLCHAR;
13503     return buf;
13504 }
13505
13506 #define PGN_MAX_LINE 75
13507
13508 #define PGN_SIDE_WHITE  0
13509 #define PGN_SIDE_BLACK  1
13510
13511 static int
13512 FindFirstMoveOutOfBook (int side)
13513 {
13514     int result = -1;
13515
13516     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13517         int index = backwardMostMove;
13518         int has_book_hit = 0;
13519
13520         if( (index % 2) != side ) {
13521             index++;
13522         }
13523
13524         while( index < forwardMostMove ) {
13525             /* Check to see if engine is in book */
13526             int depth = pvInfoList[index].depth;
13527             int score = pvInfoList[index].score;
13528             int in_book = 0;
13529
13530             if( depth <= 2 ) {
13531                 in_book = 1;
13532             }
13533             else if( score == 0 && depth == 63 ) {
13534                 in_book = 1; /* Zappa */
13535             }
13536             else if( score == 2 && depth == 99 ) {
13537                 in_book = 1; /* Abrok */
13538             }
13539
13540             has_book_hit += in_book;
13541
13542             if( ! in_book ) {
13543                 result = index;
13544
13545                 break;
13546             }
13547
13548             index += 2;
13549         }
13550     }
13551
13552     return result;
13553 }
13554
13555 void
13556 GetOutOfBookInfo (char * buf)
13557 {
13558     int oob[2];
13559     int i;
13560     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13561
13562     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13563     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13564
13565     *buf = '\0';
13566
13567     if( oob[0] >= 0 || oob[1] >= 0 ) {
13568         for( i=0; i<2; i++ ) {
13569             int idx = oob[i];
13570
13571             if( idx >= 0 ) {
13572                 if( i > 0 && oob[0] >= 0 ) {
13573                     strcat( buf, "   " );
13574                 }
13575
13576                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13577                 sprintf( buf+strlen(buf), "%s%.2f",
13578                     pvInfoList[idx].score >= 0 ? "+" : "",
13579                     pvInfoList[idx].score / 100.0 );
13580             }
13581         }
13582     }
13583 }
13584
13585 /* Save game in PGN style */
13586 static void
13587 SaveGamePGN2 (FILE *f)
13588 {
13589     int i, offset, linelen, newblock;
13590 //    char *movetext;
13591     char numtext[32];
13592     int movelen, numlen, blank;
13593     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13594
13595     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13596
13597     PrintPGNTags(f, &gameInfo);
13598
13599     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13600
13601     if (backwardMostMove > 0 || startedFromSetupPosition) {
13602         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13603         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13604         fprintf(f, "\n{--------------\n");
13605         PrintPosition(f, backwardMostMove);
13606         fprintf(f, "--------------}\n");
13607         free(fen);
13608     }
13609     else {
13610         /* [AS] Out of book annotation */
13611         if( appData.saveOutOfBookInfo ) {
13612             char buf[64];
13613
13614             GetOutOfBookInfo( buf );
13615
13616             if( buf[0] != '\0' ) {
13617                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13618             }
13619         }
13620
13621         fprintf(f, "\n");
13622     }
13623
13624     i = backwardMostMove;
13625     linelen = 0;
13626     newblock = TRUE;
13627
13628     while (i < forwardMostMove) {
13629         /* Print comments preceding this move */
13630         if (commentList[i] != NULL) {
13631             if (linelen > 0) fprintf(f, "\n");
13632             fprintf(f, "%s", commentList[i]);
13633             linelen = 0;
13634             newblock = TRUE;
13635         }
13636
13637         /* Format move number */
13638         if ((i % 2) == 0)
13639           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13640         else
13641           if (newblock)
13642             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13643           else
13644             numtext[0] = NULLCHAR;
13645
13646         numlen = strlen(numtext);
13647         newblock = FALSE;
13648
13649         /* Print move number */
13650         blank = linelen > 0 && numlen > 0;
13651         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13652             fprintf(f, "\n");
13653             linelen = 0;
13654             blank = 0;
13655         }
13656         if (blank) {
13657             fprintf(f, " ");
13658             linelen++;
13659         }
13660         fprintf(f, "%s", numtext);
13661         linelen += numlen;
13662
13663         /* Get move */
13664         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13665         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13666
13667         /* Print move */
13668         blank = linelen > 0 && movelen > 0;
13669         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13670             fprintf(f, "\n");
13671             linelen = 0;
13672             blank = 0;
13673         }
13674         if (blank) {
13675             fprintf(f, " ");
13676             linelen++;
13677         }
13678         fprintf(f, "%s", move_buffer);
13679         linelen += movelen;
13680
13681         /* [AS] Add PV info if present */
13682         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13683             /* [HGM] add time */
13684             char buf[MSG_SIZ]; int seconds;
13685
13686             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13687
13688             if( seconds <= 0)
13689               buf[0] = 0;
13690             else
13691               if( seconds < 30 )
13692                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13693               else
13694                 {
13695                   seconds = (seconds + 4)/10; // round to full seconds
13696                   if( seconds < 60 )
13697                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13698                   else
13699                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13700                 }
13701
13702             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13703                       pvInfoList[i].score >= 0 ? "+" : "",
13704                       pvInfoList[i].score / 100.0,
13705                       pvInfoList[i].depth,
13706                       buf );
13707
13708             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13709
13710             /* Print score/depth */
13711             blank = linelen > 0 && movelen > 0;
13712             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13713                 fprintf(f, "\n");
13714                 linelen = 0;
13715                 blank = 0;
13716             }
13717             if (blank) {
13718                 fprintf(f, " ");
13719                 linelen++;
13720             }
13721             fprintf(f, "%s", move_buffer);
13722             linelen += movelen;
13723         }
13724
13725         i++;
13726     }
13727
13728     /* Start a new line */
13729     if (linelen > 0) fprintf(f, "\n");
13730
13731     /* Print comments after last move */
13732     if (commentList[i] != NULL) {
13733         fprintf(f, "%s\n", commentList[i]);
13734     }
13735
13736     /* Print result */
13737     if (gameInfo.resultDetails != NULL &&
13738         gameInfo.resultDetails[0] != NULLCHAR) {
13739         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13740         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13741            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13742             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13743         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13744     } else {
13745         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13746     }
13747 }
13748
13749 /* Save game in PGN style and close the file */
13750 int
13751 SaveGamePGN (FILE *f)
13752 {
13753     SaveGamePGN2(f);
13754     fclose(f);
13755     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13756     return TRUE;
13757 }
13758
13759 /* Save game in old style and close the file */
13760 int
13761 SaveGameOldStyle (FILE *f)
13762 {
13763     int i, offset;
13764     time_t tm;
13765
13766     tm = time((time_t *) NULL);
13767
13768     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13769     PrintOpponents(f);
13770
13771     if (backwardMostMove > 0 || startedFromSetupPosition) {
13772         fprintf(f, "\n[--------------\n");
13773         PrintPosition(f, backwardMostMove);
13774         fprintf(f, "--------------]\n");
13775     } else {
13776         fprintf(f, "\n");
13777     }
13778
13779     i = backwardMostMove;
13780     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13781
13782     while (i < forwardMostMove) {
13783         if (commentList[i] != NULL) {
13784             fprintf(f, "[%s]\n", commentList[i]);
13785         }
13786
13787         if ((i % 2) == 1) {
13788             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13789             i++;
13790         } else {
13791             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13792             i++;
13793             if (commentList[i] != NULL) {
13794                 fprintf(f, "\n");
13795                 continue;
13796             }
13797             if (i >= forwardMostMove) {
13798                 fprintf(f, "\n");
13799                 break;
13800             }
13801             fprintf(f, "%s\n", parseList[i]);
13802             i++;
13803         }
13804     }
13805
13806     if (commentList[i] != NULL) {
13807         fprintf(f, "[%s]\n", commentList[i]);
13808     }
13809
13810     /* This isn't really the old style, but it's close enough */
13811     if (gameInfo.resultDetails != NULL &&
13812         gameInfo.resultDetails[0] != NULLCHAR) {
13813         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13814                 gameInfo.resultDetails);
13815     } else {
13816         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13817     }
13818
13819     fclose(f);
13820     return TRUE;
13821 }
13822
13823 /* Save the current game to open file f and close the file */
13824 int
13825 SaveGame (FILE *f, int dummy, char *dummy2)
13826 {
13827     if (gameMode == EditPosition) EditPositionDone(TRUE);
13828     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13829     if (appData.oldSaveStyle)
13830       return SaveGameOldStyle(f);
13831     else
13832       return SaveGamePGN(f);
13833 }
13834
13835 /* Save the current position to the given file */
13836 int
13837 SavePositionToFile (char *filename)
13838 {
13839     FILE *f;
13840     char buf[MSG_SIZ];
13841
13842     if (strcmp(filename, "-") == 0) {
13843         return SavePosition(stdout, 0, NULL);
13844     } else {
13845         f = fopen(filename, "a");
13846         if (f == NULL) {
13847             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13848             DisplayError(buf, errno);
13849             return FALSE;
13850         } else {
13851             safeStrCpy(buf, lastMsg, MSG_SIZ);
13852             DisplayMessage(_("Waiting for access to save file"), "");
13853             flock(fileno(f), LOCK_EX); // [HGM] lock
13854             DisplayMessage(_("Saving position"), "");
13855             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13856             SavePosition(f, 0, NULL);
13857             DisplayMessage(buf, "");
13858             return TRUE;
13859         }
13860     }
13861 }
13862
13863 /* Save the current position to the given open file and close the file */
13864 int
13865 SavePosition (FILE *f, int dummy, char *dummy2)
13866 {
13867     time_t tm;
13868     char *fen;
13869
13870     if (gameMode == EditPosition) EditPositionDone(TRUE);
13871     if (appData.oldSaveStyle) {
13872         tm = time((time_t *) NULL);
13873
13874         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13875         PrintOpponents(f);
13876         fprintf(f, "[--------------\n");
13877         PrintPosition(f, currentMove);
13878         fprintf(f, "--------------]\n");
13879     } else {
13880         fen = PositionToFEN(currentMove, NULL, 1);
13881         fprintf(f, "%s\n", fen);
13882         free(fen);
13883     }
13884     fclose(f);
13885     return TRUE;
13886 }
13887
13888 void
13889 ReloadCmailMsgEvent (int unregister)
13890 {
13891 #if !WIN32
13892     static char *inFilename = NULL;
13893     static char *outFilename;
13894     int i;
13895     struct stat inbuf, outbuf;
13896     int status;
13897
13898     /* Any registered moves are unregistered if unregister is set, */
13899     /* i.e. invoked by the signal handler */
13900     if (unregister) {
13901         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13902             cmailMoveRegistered[i] = FALSE;
13903             if (cmailCommentList[i] != NULL) {
13904                 free(cmailCommentList[i]);
13905                 cmailCommentList[i] = NULL;
13906             }
13907         }
13908         nCmailMovesRegistered = 0;
13909     }
13910
13911     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13912         cmailResult[i] = CMAIL_NOT_RESULT;
13913     }
13914     nCmailResults = 0;
13915
13916     if (inFilename == NULL) {
13917         /* Because the filenames are static they only get malloced once  */
13918         /* and they never get freed                                      */
13919         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13920         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13921
13922         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13923         sprintf(outFilename, "%s.out", appData.cmailGameName);
13924     }
13925
13926     status = stat(outFilename, &outbuf);
13927     if (status < 0) {
13928         cmailMailedMove = FALSE;
13929     } else {
13930         status = stat(inFilename, &inbuf);
13931         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13932     }
13933
13934     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13935        counts the games, notes how each one terminated, etc.
13936
13937        It would be nice to remove this kludge and instead gather all
13938        the information while building the game list.  (And to keep it
13939        in the game list nodes instead of having a bunch of fixed-size
13940        parallel arrays.)  Note this will require getting each game's
13941        termination from the PGN tags, as the game list builder does
13942        not process the game moves.  --mann
13943        */
13944     cmailMsgLoaded = TRUE;
13945     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13946
13947     /* Load first game in the file or popup game menu */
13948     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13949
13950 #endif /* !WIN32 */
13951     return;
13952 }
13953
13954 int
13955 RegisterMove ()
13956 {
13957     FILE *f;
13958     char string[MSG_SIZ];
13959
13960     if (   cmailMailedMove
13961         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13962         return TRUE;            /* Allow free viewing  */
13963     }
13964
13965     /* Unregister move to ensure that we don't leave RegisterMove        */
13966     /* with the move registered when the conditions for registering no   */
13967     /* longer hold                                                       */
13968     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13969         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13970         nCmailMovesRegistered --;
13971
13972         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13973           {
13974               free(cmailCommentList[lastLoadGameNumber - 1]);
13975               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13976           }
13977     }
13978
13979     if (cmailOldMove == -1) {
13980         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13981         return FALSE;
13982     }
13983
13984     if (currentMove > cmailOldMove + 1) {
13985         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13986         return FALSE;
13987     }
13988
13989     if (currentMove < cmailOldMove) {
13990         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13991         return FALSE;
13992     }
13993
13994     if (forwardMostMove > currentMove) {
13995         /* Silently truncate extra moves */
13996         TruncateGame();
13997     }
13998
13999     if (   (currentMove == cmailOldMove + 1)
14000         || (   (currentMove == cmailOldMove)
14001             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14002                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14003         if (gameInfo.result != GameUnfinished) {
14004             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14005         }
14006
14007         if (commentList[currentMove] != NULL) {
14008             cmailCommentList[lastLoadGameNumber - 1]
14009               = StrSave(commentList[currentMove]);
14010         }
14011         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14012
14013         if (appData.debugMode)
14014           fprintf(debugFP, "Saving %s for game %d\n",
14015                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14016
14017         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14018
14019         f = fopen(string, "w");
14020         if (appData.oldSaveStyle) {
14021             SaveGameOldStyle(f); /* also closes the file */
14022
14023             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14024             f = fopen(string, "w");
14025             SavePosition(f, 0, NULL); /* also closes the file */
14026         } else {
14027             fprintf(f, "{--------------\n");
14028             PrintPosition(f, currentMove);
14029             fprintf(f, "--------------}\n\n");
14030
14031             SaveGame(f, 0, NULL); /* also closes the file*/
14032         }
14033
14034         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14035         nCmailMovesRegistered ++;
14036     } else if (nCmailGames == 1) {
14037         DisplayError(_("You have not made a move yet"), 0);
14038         return FALSE;
14039     }
14040
14041     return TRUE;
14042 }
14043
14044 void
14045 MailMoveEvent ()
14046 {
14047 #if !WIN32
14048     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14049     FILE *commandOutput;
14050     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14051     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14052     int nBuffers;
14053     int i;
14054     int archived;
14055     char *arcDir;
14056
14057     if (! cmailMsgLoaded) {
14058         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14059         return;
14060     }
14061
14062     if (nCmailGames == nCmailResults) {
14063         DisplayError(_("No unfinished games"), 0);
14064         return;
14065     }
14066
14067 #if CMAIL_PROHIBIT_REMAIL
14068     if (cmailMailedMove) {
14069       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);
14070         DisplayError(msg, 0);
14071         return;
14072     }
14073 #endif
14074
14075     if (! (cmailMailedMove || RegisterMove())) return;
14076
14077     if (   cmailMailedMove
14078         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14079       snprintf(string, MSG_SIZ, partCommandString,
14080                appData.debugMode ? " -v" : "", appData.cmailGameName);
14081         commandOutput = popen(string, "r");
14082
14083         if (commandOutput == NULL) {
14084             DisplayError(_("Failed to invoke cmail"), 0);
14085         } else {
14086             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14087                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14088             }
14089             if (nBuffers > 1) {
14090                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14091                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14092                 nBytes = MSG_SIZ - 1;
14093             } else {
14094                 (void) memcpy(msg, buffer, nBytes);
14095             }
14096             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14097
14098             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14099                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14100
14101                 archived = TRUE;
14102                 for (i = 0; i < nCmailGames; i ++) {
14103                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14104                         archived = FALSE;
14105                     }
14106                 }
14107                 if (   archived
14108                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14109                         != NULL)) {
14110                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14111                            arcDir,
14112                            appData.cmailGameName,
14113                            gameInfo.date);
14114                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14115                     cmailMsgLoaded = FALSE;
14116                 }
14117             }
14118
14119             DisplayInformation(msg);
14120             pclose(commandOutput);
14121         }
14122     } else {
14123         if ((*cmailMsg) != '\0') {
14124             DisplayInformation(cmailMsg);
14125         }
14126     }
14127
14128     return;
14129 #endif /* !WIN32 */
14130 }
14131
14132 char *
14133 CmailMsg ()
14134 {
14135 #if WIN32
14136     return NULL;
14137 #else
14138     int  prependComma = 0;
14139     char number[5];
14140     char string[MSG_SIZ];       /* Space for game-list */
14141     int  i;
14142
14143     if (!cmailMsgLoaded) return "";
14144
14145     if (cmailMailedMove) {
14146       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14147     } else {
14148         /* Create a list of games left */
14149       snprintf(string, MSG_SIZ, "[");
14150         for (i = 0; i < nCmailGames; i ++) {
14151             if (! (   cmailMoveRegistered[i]
14152                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14153                 if (prependComma) {
14154                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14155                 } else {
14156                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14157                     prependComma = 1;
14158                 }
14159
14160                 strcat(string, number);
14161             }
14162         }
14163         strcat(string, "]");
14164
14165         if (nCmailMovesRegistered + nCmailResults == 0) {
14166             switch (nCmailGames) {
14167               case 1:
14168                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14169                 break;
14170
14171               case 2:
14172                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14173                 break;
14174
14175               default:
14176                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14177                          nCmailGames);
14178                 break;
14179             }
14180         } else {
14181             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14182               case 1:
14183                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14184                          string);
14185                 break;
14186
14187               case 0:
14188                 if (nCmailResults == nCmailGames) {
14189                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14190                 } else {
14191                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14192                 }
14193                 break;
14194
14195               default:
14196                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14197                          string);
14198             }
14199         }
14200     }
14201     return cmailMsg;
14202 #endif /* WIN32 */
14203 }
14204
14205 void
14206 ResetGameEvent ()
14207 {
14208     if (gameMode == Training)
14209       SetTrainingModeOff();
14210
14211     Reset(TRUE, TRUE);
14212     cmailMsgLoaded = FALSE;
14213     if (appData.icsActive) {
14214       SendToICS(ics_prefix);
14215       SendToICS("refresh\n");
14216     }
14217 }
14218
14219 void
14220 ExitEvent (int status)
14221 {
14222     exiting++;
14223     if (exiting > 2) {
14224       /* Give up on clean exit */
14225       exit(status);
14226     }
14227     if (exiting > 1) {
14228       /* Keep trying for clean exit */
14229       return;
14230     }
14231
14232     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14233     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14234
14235     if (telnetISR != NULL) {
14236       RemoveInputSource(telnetISR);
14237     }
14238     if (icsPR != NoProc) {
14239       DestroyChildProcess(icsPR, TRUE);
14240     }
14241
14242     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14243     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14244
14245     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14246     /* make sure this other one finishes before killing it!                  */
14247     if(endingGame) { int count = 0;
14248         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14249         while(endingGame && count++ < 10) DoSleep(1);
14250         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14251     }
14252
14253     /* Kill off chess programs */
14254     if (first.pr != NoProc) {
14255         ExitAnalyzeMode();
14256
14257         DoSleep( appData.delayBeforeQuit );
14258         SendToProgram("quit\n", &first);
14259         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14260     }
14261     if (second.pr != NoProc) {
14262         DoSleep( appData.delayBeforeQuit );
14263         SendToProgram("quit\n", &second);
14264         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14265     }
14266     if (first.isr != NULL) {
14267         RemoveInputSource(first.isr);
14268     }
14269     if (second.isr != NULL) {
14270         RemoveInputSource(second.isr);
14271     }
14272
14273     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14274     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14275
14276     ShutDownFrontEnd();
14277     exit(status);
14278 }
14279
14280 void
14281 PauseEngine (ChessProgramState *cps)
14282 {
14283     SendToProgram("pause\n", cps);
14284     cps->pause = 2;
14285 }
14286
14287 void
14288 UnPauseEngine (ChessProgramState *cps)
14289 {
14290     SendToProgram("resume\n", cps);
14291     cps->pause = 1;
14292 }
14293
14294 void
14295 PauseEvent ()
14296 {
14297     if (appData.debugMode)
14298         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14299     if (pausing) {
14300         pausing = FALSE;
14301         ModeHighlight();
14302         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14303             StartClocks();
14304             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14305                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14306                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14307             }
14308             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14309             HandleMachineMove(stashedInputMove, stalledEngine);
14310             stalledEngine = NULL;
14311             return;
14312         }
14313         if (gameMode == MachinePlaysWhite ||
14314             gameMode == TwoMachinesPlay   ||
14315             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14316             if(first.pause)  UnPauseEngine(&first);
14317             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14318             if(second.pause) UnPauseEngine(&second);
14319             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14320             StartClocks();
14321         } else {
14322             DisplayBothClocks();
14323         }
14324         if (gameMode == PlayFromGameFile) {
14325             if (appData.timeDelay >= 0)
14326                 AutoPlayGameLoop();
14327         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14328             Reset(FALSE, TRUE);
14329             SendToICS(ics_prefix);
14330             SendToICS("refresh\n");
14331         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14332             ForwardInner(forwardMostMove);
14333         }
14334         pauseExamInvalid = FALSE;
14335     } else {
14336         switch (gameMode) {
14337           default:
14338             return;
14339           case IcsExamining:
14340             pauseExamForwardMostMove = forwardMostMove;
14341             pauseExamInvalid = FALSE;
14342             /* fall through */
14343           case IcsObserving:
14344           case IcsPlayingWhite:
14345           case IcsPlayingBlack:
14346             pausing = TRUE;
14347             ModeHighlight();
14348             return;
14349           case PlayFromGameFile:
14350             (void) StopLoadGameTimer();
14351             pausing = TRUE;
14352             ModeHighlight();
14353             break;
14354           case BeginningOfGame:
14355             if (appData.icsActive) return;
14356             /* else fall through */
14357           case MachinePlaysWhite:
14358           case MachinePlaysBlack:
14359           case TwoMachinesPlay:
14360             if (forwardMostMove == 0)
14361               return;           /* don't pause if no one has moved */
14362             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14363                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14364                 if(onMove->pause) {           // thinking engine can be paused
14365                     PauseEngine(onMove);      // do it
14366                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14367                         PauseEngine(onMove->other);
14368                     else
14369                         SendToProgram("easy\n", onMove->other);
14370                     StopClocks();
14371                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14372             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14373                 if(first.pause) {
14374                     PauseEngine(&first);
14375                     StopClocks();
14376                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14377             } else { // human on move, pause pondering by either method
14378                 if(first.pause)
14379                     PauseEngine(&first);
14380                 else if(appData.ponderNextMove)
14381                     SendToProgram("easy\n", &first);
14382                 StopClocks();
14383             }
14384             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14385           case AnalyzeMode:
14386             pausing = TRUE;
14387             ModeHighlight();
14388             break;
14389         }
14390     }
14391 }
14392
14393 void
14394 EditCommentEvent ()
14395 {
14396     char title[MSG_SIZ];
14397
14398     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14399       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14400     } else {
14401       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14402                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14403                parseList[currentMove - 1]);
14404     }
14405
14406     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14407 }
14408
14409
14410 void
14411 EditTagsEvent ()
14412 {
14413     char *tags = PGNTags(&gameInfo);
14414     bookUp = FALSE;
14415     EditTagsPopUp(tags, NULL);
14416     free(tags);
14417 }
14418
14419 void
14420 ToggleSecond ()
14421 {
14422   if(second.analyzing) {
14423     SendToProgram("exit\n", &second);
14424     second.analyzing = FALSE;
14425   } else {
14426     if (second.pr == NoProc) StartChessProgram(&second);
14427     InitChessProgram(&second, FALSE);
14428     FeedMovesToProgram(&second, currentMove);
14429
14430     SendToProgram("analyze\n", &second);
14431     second.analyzing = TRUE;
14432   }
14433 }
14434
14435 /* Toggle ShowThinking */
14436 void
14437 ToggleShowThinking()
14438 {
14439   appData.showThinking = !appData.showThinking;
14440   ShowThinkingEvent();
14441 }
14442
14443 int
14444 AnalyzeModeEvent ()
14445 {
14446     char buf[MSG_SIZ];
14447
14448     if (!first.analysisSupport) {
14449       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14450       DisplayError(buf, 0);
14451       return 0;
14452     }
14453     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14454     if (appData.icsActive) {
14455         if (gameMode != IcsObserving) {
14456           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14457             DisplayError(buf, 0);
14458             /* secure check */
14459             if (appData.icsEngineAnalyze) {
14460                 if (appData.debugMode)
14461                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14462                 ExitAnalyzeMode();
14463                 ModeHighlight();
14464             }
14465             return 0;
14466         }
14467         /* if enable, user wants to disable icsEngineAnalyze */
14468         if (appData.icsEngineAnalyze) {
14469                 ExitAnalyzeMode();
14470                 ModeHighlight();
14471                 return 0;
14472         }
14473         appData.icsEngineAnalyze = TRUE;
14474         if (appData.debugMode)
14475             fprintf(debugFP, "ICS engine analyze starting... \n");
14476     }
14477
14478     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14479     if (appData.noChessProgram || gameMode == AnalyzeMode)
14480       return 0;
14481
14482     if (gameMode != AnalyzeFile) {
14483         if (!appData.icsEngineAnalyze) {
14484                EditGameEvent();
14485                if (gameMode != EditGame) return 0;
14486         }
14487         if (!appData.showThinking) ToggleShowThinking();
14488         ResurrectChessProgram();
14489         SendToProgram("analyze\n", &first);
14490         first.analyzing = TRUE;
14491         /*first.maybeThinking = TRUE;*/
14492         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14493         EngineOutputPopUp();
14494     }
14495     if (!appData.icsEngineAnalyze) {
14496         gameMode = AnalyzeMode;
14497         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14498     }
14499     pausing = FALSE;
14500     ModeHighlight();
14501     SetGameInfo();
14502
14503     StartAnalysisClock();
14504     GetTimeMark(&lastNodeCountTime);
14505     lastNodeCount = 0;
14506     return 1;
14507 }
14508
14509 void
14510 AnalyzeFileEvent ()
14511 {
14512     if (appData.noChessProgram || gameMode == AnalyzeFile)
14513       return;
14514
14515     if (!first.analysisSupport) {
14516       char buf[MSG_SIZ];
14517       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14518       DisplayError(buf, 0);
14519       return;
14520     }
14521
14522     if (gameMode != AnalyzeMode) {
14523         keepInfo = 1; // mere annotating should not alter PGN tags
14524         EditGameEvent();
14525         keepInfo = 0;
14526         if (gameMode != EditGame) return;
14527         if (!appData.showThinking) ToggleShowThinking();
14528         ResurrectChessProgram();
14529         SendToProgram("analyze\n", &first);
14530         first.analyzing = TRUE;
14531         /*first.maybeThinking = TRUE;*/
14532         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14533         EngineOutputPopUp();
14534     }
14535     gameMode = AnalyzeFile;
14536     pausing = FALSE;
14537     ModeHighlight();
14538
14539     StartAnalysisClock();
14540     GetTimeMark(&lastNodeCountTime);
14541     lastNodeCount = 0;
14542     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14543     AnalysisPeriodicEvent(1);
14544 }
14545
14546 void
14547 MachineWhiteEvent ()
14548 {
14549     char buf[MSG_SIZ];
14550     char *bookHit = NULL;
14551
14552     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14553       return;
14554
14555
14556     if (gameMode == PlayFromGameFile ||
14557         gameMode == TwoMachinesPlay  ||
14558         gameMode == Training         ||
14559         gameMode == AnalyzeMode      ||
14560         gameMode == EndOfGame)
14561         EditGameEvent();
14562
14563     if (gameMode == EditPosition)
14564         EditPositionDone(TRUE);
14565
14566     if (!WhiteOnMove(currentMove)) {
14567         DisplayError(_("It is not White's turn"), 0);
14568         return;
14569     }
14570
14571     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14572       ExitAnalyzeMode();
14573
14574     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14575         gameMode == AnalyzeFile)
14576         TruncateGame();
14577
14578     ResurrectChessProgram();    /* in case it isn't running */
14579     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14580         gameMode = MachinePlaysWhite;
14581         ResetClocks();
14582     } else
14583     gameMode = MachinePlaysWhite;
14584     pausing = FALSE;
14585     ModeHighlight();
14586     SetGameInfo();
14587     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14588     DisplayTitle(buf);
14589     if (first.sendName) {
14590       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14591       SendToProgram(buf, &first);
14592     }
14593     if (first.sendTime) {
14594       if (first.useColors) {
14595         SendToProgram("black\n", &first); /*gnu kludge*/
14596       }
14597       SendTimeRemaining(&first, TRUE);
14598     }
14599     if (first.useColors) {
14600       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14601     }
14602     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14603     SetMachineThinkingEnables();
14604     first.maybeThinking = TRUE;
14605     StartClocks();
14606     firstMove = FALSE;
14607
14608     if (appData.autoFlipView && !flipView) {
14609       flipView = !flipView;
14610       DrawPosition(FALSE, NULL);
14611       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14612     }
14613
14614     if(bookHit) { // [HGM] book: simulate book reply
14615         static char bookMove[MSG_SIZ]; // a bit generous?
14616
14617         programStats.nodes = programStats.depth = programStats.time =
14618         programStats.score = programStats.got_only_move = 0;
14619         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14620
14621         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14622         strcat(bookMove, bookHit);
14623         HandleMachineMove(bookMove, &first);
14624     }
14625 }
14626
14627 void
14628 MachineBlackEvent ()
14629 {
14630   char buf[MSG_SIZ];
14631   char *bookHit = NULL;
14632
14633     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14634         return;
14635
14636
14637     if (gameMode == PlayFromGameFile ||
14638         gameMode == TwoMachinesPlay  ||
14639         gameMode == Training         ||
14640         gameMode == AnalyzeMode      ||
14641         gameMode == EndOfGame)
14642         EditGameEvent();
14643
14644     if (gameMode == EditPosition)
14645         EditPositionDone(TRUE);
14646
14647     if (WhiteOnMove(currentMove)) {
14648         DisplayError(_("It is not Black's turn"), 0);
14649         return;
14650     }
14651
14652     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14653       ExitAnalyzeMode();
14654
14655     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14656         gameMode == AnalyzeFile)
14657         TruncateGame();
14658
14659     ResurrectChessProgram();    /* in case it isn't running */
14660     gameMode = MachinePlaysBlack;
14661     pausing = FALSE;
14662     ModeHighlight();
14663     SetGameInfo();
14664     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14665     DisplayTitle(buf);
14666     if (first.sendName) {
14667       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14668       SendToProgram(buf, &first);
14669     }
14670     if (first.sendTime) {
14671       if (first.useColors) {
14672         SendToProgram("white\n", &first); /*gnu kludge*/
14673       }
14674       SendTimeRemaining(&first, FALSE);
14675     }
14676     if (first.useColors) {
14677       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14678     }
14679     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14680     SetMachineThinkingEnables();
14681     first.maybeThinking = TRUE;
14682     StartClocks();
14683
14684     if (appData.autoFlipView && flipView) {
14685       flipView = !flipView;
14686       DrawPosition(FALSE, NULL);
14687       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14688     }
14689     if(bookHit) { // [HGM] book: simulate book reply
14690         static char bookMove[MSG_SIZ]; // a bit generous?
14691
14692         programStats.nodes = programStats.depth = programStats.time =
14693         programStats.score = programStats.got_only_move = 0;
14694         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14695
14696         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14697         strcat(bookMove, bookHit);
14698         HandleMachineMove(bookMove, &first);
14699     }
14700 }
14701
14702
14703 void
14704 DisplayTwoMachinesTitle ()
14705 {
14706     char buf[MSG_SIZ];
14707     if (appData.matchGames > 0) {
14708         if(appData.tourneyFile[0]) {
14709           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14710                    gameInfo.white, _("vs."), gameInfo.black,
14711                    nextGame+1, appData.matchGames+1,
14712                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14713         } else
14714         if (first.twoMachinesColor[0] == 'w') {
14715           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14716                    gameInfo.white, _("vs."),  gameInfo.black,
14717                    first.matchWins, second.matchWins,
14718                    matchGame - 1 - (first.matchWins + second.matchWins));
14719         } else {
14720           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14721                    gameInfo.white, _("vs."), gameInfo.black,
14722                    second.matchWins, first.matchWins,
14723                    matchGame - 1 - (first.matchWins + second.matchWins));
14724         }
14725     } else {
14726       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14727     }
14728     DisplayTitle(buf);
14729 }
14730
14731 void
14732 SettingsMenuIfReady ()
14733 {
14734   if (second.lastPing != second.lastPong) {
14735     DisplayMessage("", _("Waiting for second chess program"));
14736     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14737     return;
14738   }
14739   ThawUI();
14740   DisplayMessage("", "");
14741   SettingsPopUp(&second);
14742 }
14743
14744 int
14745 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14746 {
14747     char buf[MSG_SIZ];
14748     if (cps->pr == NoProc) {
14749         StartChessProgram(cps);
14750         if (cps->protocolVersion == 1) {
14751           retry();
14752           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14753         } else {
14754           /* kludge: allow timeout for initial "feature" command */
14755           if(retry != TwoMachinesEventIfReady) FreezeUI();
14756           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14757           DisplayMessage("", buf);
14758           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14759         }
14760         return 1;
14761     }
14762     return 0;
14763 }
14764
14765 void
14766 TwoMachinesEvent P((void))
14767 {
14768     int i;
14769     char buf[MSG_SIZ];
14770     ChessProgramState *onmove;
14771     char *bookHit = NULL;
14772     static int stalling = 0;
14773     TimeMark now;
14774     long wait;
14775
14776     if (appData.noChessProgram) return;
14777
14778     switch (gameMode) {
14779       case TwoMachinesPlay:
14780         return;
14781       case MachinePlaysWhite:
14782       case MachinePlaysBlack:
14783         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14784             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14785             return;
14786         }
14787         /* fall through */
14788       case BeginningOfGame:
14789       case PlayFromGameFile:
14790       case EndOfGame:
14791         EditGameEvent();
14792         if (gameMode != EditGame) return;
14793         break;
14794       case EditPosition:
14795         EditPositionDone(TRUE);
14796         break;
14797       case AnalyzeMode:
14798       case AnalyzeFile:
14799         ExitAnalyzeMode();
14800         break;
14801       case EditGame:
14802       default:
14803         break;
14804     }
14805
14806 //    forwardMostMove = currentMove;
14807     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14808     startingEngine = TRUE;
14809
14810     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14811
14812     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14813     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14814       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14815       return;
14816     }
14817     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14818
14819     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14820                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14821         startingEngine = matchMode = FALSE;
14822         DisplayError("second engine does not play this", 0);
14823         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14824         EditGameEvent(); // switch back to EditGame mode
14825         return;
14826     }
14827
14828     if(!stalling) {
14829       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14830       SendToProgram("force\n", &second);
14831       stalling = 1;
14832       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14833       return;
14834     }
14835     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14836     if(appData.matchPause>10000 || appData.matchPause<10)
14837                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14838     wait = SubtractTimeMarks(&now, &pauseStart);
14839     if(wait < appData.matchPause) {
14840         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14841         return;
14842     }
14843     // we are now committed to starting the game
14844     stalling = 0;
14845     DisplayMessage("", "");
14846     if (startedFromSetupPosition) {
14847         SendBoard(&second, backwardMostMove);
14848     if (appData.debugMode) {
14849         fprintf(debugFP, "Two Machines\n");
14850     }
14851     }
14852     for (i = backwardMostMove; i < forwardMostMove; i++) {
14853         SendMoveToProgram(i, &second);
14854     }
14855
14856     gameMode = TwoMachinesPlay;
14857     pausing = startingEngine = FALSE;
14858     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14859     SetGameInfo();
14860     DisplayTwoMachinesTitle();
14861     firstMove = TRUE;
14862     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14863         onmove = &first;
14864     } else {
14865         onmove = &second;
14866     }
14867     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14868     SendToProgram(first.computerString, &first);
14869     if (first.sendName) {
14870       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14871       SendToProgram(buf, &first);
14872     }
14873     SendToProgram(second.computerString, &second);
14874     if (second.sendName) {
14875       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14876       SendToProgram(buf, &second);
14877     }
14878
14879     ResetClocks();
14880     if (!first.sendTime || !second.sendTime) {
14881         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14882         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14883     }
14884     if (onmove->sendTime) {
14885       if (onmove->useColors) {
14886         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14887       }
14888       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14889     }
14890     if (onmove->useColors) {
14891       SendToProgram(onmove->twoMachinesColor, onmove);
14892     }
14893     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14894 //    SendToProgram("go\n", onmove);
14895     onmove->maybeThinking = TRUE;
14896     SetMachineThinkingEnables();
14897
14898     StartClocks();
14899
14900     if(bookHit) { // [HGM] book: simulate book reply
14901         static char bookMove[MSG_SIZ]; // a bit generous?
14902
14903         programStats.nodes = programStats.depth = programStats.time =
14904         programStats.score = programStats.got_only_move = 0;
14905         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14906
14907         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14908         strcat(bookMove, bookHit);
14909         savedMessage = bookMove; // args for deferred call
14910         savedState = onmove;
14911         ScheduleDelayedEvent(DeferredBookMove, 1);
14912     }
14913 }
14914
14915 void
14916 TrainingEvent ()
14917 {
14918     if (gameMode == Training) {
14919       SetTrainingModeOff();
14920       gameMode = PlayFromGameFile;
14921       DisplayMessage("", _("Training mode off"));
14922     } else {
14923       gameMode = Training;
14924       animateTraining = appData.animate;
14925
14926       /* make sure we are not already at the end of the game */
14927       if (currentMove < forwardMostMove) {
14928         SetTrainingModeOn();
14929         DisplayMessage("", _("Training mode on"));
14930       } else {
14931         gameMode = PlayFromGameFile;
14932         DisplayError(_("Already at end of game"), 0);
14933       }
14934     }
14935     ModeHighlight();
14936 }
14937
14938 void
14939 IcsClientEvent ()
14940 {
14941     if (!appData.icsActive) return;
14942     switch (gameMode) {
14943       case IcsPlayingWhite:
14944       case IcsPlayingBlack:
14945       case IcsObserving:
14946       case IcsIdle:
14947       case BeginningOfGame:
14948       case IcsExamining:
14949         return;
14950
14951       case EditGame:
14952         break;
14953
14954       case EditPosition:
14955         EditPositionDone(TRUE);
14956         break;
14957
14958       case AnalyzeMode:
14959       case AnalyzeFile:
14960         ExitAnalyzeMode();
14961         break;
14962
14963       default:
14964         EditGameEvent();
14965         break;
14966     }
14967
14968     gameMode = IcsIdle;
14969     ModeHighlight();
14970     return;
14971 }
14972
14973 void
14974 EditGameEvent ()
14975 {
14976     int i;
14977
14978     switch (gameMode) {
14979       case Training:
14980         SetTrainingModeOff();
14981         break;
14982       case MachinePlaysWhite:
14983       case MachinePlaysBlack:
14984       case BeginningOfGame:
14985         SendToProgram("force\n", &first);
14986         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
14987             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
14988                 char buf[MSG_SIZ];
14989                 abortEngineThink = TRUE;
14990                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
14991                 SendToProgram(buf, &first);
14992                 DisplayMessage("Aborting engine think", "");
14993                 FreezeUI();
14994             }
14995         }
14996         SetUserThinkingEnables();
14997         break;
14998       case PlayFromGameFile:
14999         (void) StopLoadGameTimer();
15000         if (gameFileFP != NULL) {
15001             gameFileFP = NULL;
15002         }
15003         break;
15004       case EditPosition:
15005         EditPositionDone(TRUE);
15006         break;
15007       case AnalyzeMode:
15008       case AnalyzeFile:
15009         ExitAnalyzeMode();
15010         SendToProgram("force\n", &first);
15011         break;
15012       case TwoMachinesPlay:
15013         GameEnds(EndOfFile, NULL, GE_PLAYER);
15014         ResurrectChessProgram();
15015         SetUserThinkingEnables();
15016         break;
15017       case EndOfGame:
15018         ResurrectChessProgram();
15019         break;
15020       case IcsPlayingBlack:
15021       case IcsPlayingWhite:
15022         DisplayError(_("Warning: You are still playing a game"), 0);
15023         break;
15024       case IcsObserving:
15025         DisplayError(_("Warning: You are still observing a game"), 0);
15026         break;
15027       case IcsExamining:
15028         DisplayError(_("Warning: You are still examining a game"), 0);
15029         break;
15030       case IcsIdle:
15031         break;
15032       case EditGame:
15033       default:
15034         return;
15035     }
15036
15037     pausing = FALSE;
15038     StopClocks();
15039     first.offeredDraw = second.offeredDraw = 0;
15040
15041     if (gameMode == PlayFromGameFile) {
15042         whiteTimeRemaining = timeRemaining[0][currentMove];
15043         blackTimeRemaining = timeRemaining[1][currentMove];
15044         DisplayTitle("");
15045     }
15046
15047     if (gameMode == MachinePlaysWhite ||
15048         gameMode == MachinePlaysBlack ||
15049         gameMode == TwoMachinesPlay ||
15050         gameMode == EndOfGame) {
15051         i = forwardMostMove;
15052         while (i > currentMove) {
15053             SendToProgram("undo\n", &first);
15054             i--;
15055         }
15056         if(!adjustedClock) {
15057         whiteTimeRemaining = timeRemaining[0][currentMove];
15058         blackTimeRemaining = timeRemaining[1][currentMove];
15059         DisplayBothClocks();
15060         }
15061         if (whiteFlag || blackFlag) {
15062             whiteFlag = blackFlag = 0;
15063         }
15064         DisplayTitle("");
15065     }
15066
15067     gameMode = EditGame;
15068     ModeHighlight();
15069     SetGameInfo();
15070 }
15071
15072
15073 void
15074 EditPositionEvent ()
15075 {
15076     if (gameMode == EditPosition) {
15077         EditGameEvent();
15078         return;
15079     }
15080
15081     EditGameEvent();
15082     if (gameMode != EditGame) return;
15083
15084     gameMode = EditPosition;
15085     ModeHighlight();
15086     SetGameInfo();
15087     if (currentMove > 0)
15088       CopyBoard(boards[0], boards[currentMove]);
15089
15090     blackPlaysFirst = !WhiteOnMove(currentMove);
15091     ResetClocks();
15092     currentMove = forwardMostMove = backwardMostMove = 0;
15093     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15094     DisplayMove(-1);
15095     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15096 }
15097
15098 void
15099 ExitAnalyzeMode ()
15100 {
15101     /* [DM] icsEngineAnalyze - possible call from other functions */
15102     if (appData.icsEngineAnalyze) {
15103         appData.icsEngineAnalyze = FALSE;
15104
15105         DisplayMessage("",_("Close ICS engine analyze..."));
15106     }
15107     if (first.analysisSupport && first.analyzing) {
15108       SendToBoth("exit\n");
15109       first.analyzing = second.analyzing = FALSE;
15110     }
15111     thinkOutput[0] = NULLCHAR;
15112 }
15113
15114 void
15115 EditPositionDone (Boolean fakeRights)
15116 {
15117     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15118
15119     startedFromSetupPosition = TRUE;
15120     InitChessProgram(&first, FALSE);
15121     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15122       boards[0][EP_STATUS] = EP_NONE;
15123       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15124       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15125         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15126         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15127       } else boards[0][CASTLING][2] = NoRights;
15128       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15129         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15130         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15131       } else boards[0][CASTLING][5] = NoRights;
15132       if(gameInfo.variant == VariantSChess) {
15133         int i;
15134         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15135           boards[0][VIRGIN][i] = 0;
15136           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15137           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15138         }
15139       }
15140     }
15141     SendToProgram("force\n", &first);
15142     if (blackPlaysFirst) {
15143         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15144         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15145         currentMove = forwardMostMove = backwardMostMove = 1;
15146         CopyBoard(boards[1], boards[0]);
15147     } else {
15148         currentMove = forwardMostMove = backwardMostMove = 0;
15149     }
15150     SendBoard(&first, forwardMostMove);
15151     if (appData.debugMode) {
15152         fprintf(debugFP, "EditPosDone\n");
15153     }
15154     DisplayTitle("");
15155     DisplayMessage("", "");
15156     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15157     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15158     gameMode = EditGame;
15159     ModeHighlight();
15160     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15161     ClearHighlights(); /* [AS] */
15162 }
15163
15164 /* Pause for `ms' milliseconds */
15165 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15166 void
15167 TimeDelay (long ms)
15168 {
15169     TimeMark m1, m2;
15170
15171     GetTimeMark(&m1);
15172     do {
15173         GetTimeMark(&m2);
15174     } while (SubtractTimeMarks(&m2, &m1) < ms);
15175 }
15176
15177 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15178 void
15179 SendMultiLineToICS (char *buf)
15180 {
15181     char temp[MSG_SIZ+1], *p;
15182     int len;
15183
15184     len = strlen(buf);
15185     if (len > MSG_SIZ)
15186       len = MSG_SIZ;
15187
15188     strncpy(temp, buf, len);
15189     temp[len] = 0;
15190
15191     p = temp;
15192     while (*p) {
15193         if (*p == '\n' || *p == '\r')
15194           *p = ' ';
15195         ++p;
15196     }
15197
15198     strcat(temp, "\n");
15199     SendToICS(temp);
15200     SendToPlayer(temp, strlen(temp));
15201 }
15202
15203 void
15204 SetWhiteToPlayEvent ()
15205 {
15206     if (gameMode == EditPosition) {
15207         blackPlaysFirst = FALSE;
15208         DisplayBothClocks();    /* works because currentMove is 0 */
15209     } else if (gameMode == IcsExamining) {
15210         SendToICS(ics_prefix);
15211         SendToICS("tomove white\n");
15212     }
15213 }
15214
15215 void
15216 SetBlackToPlayEvent ()
15217 {
15218     if (gameMode == EditPosition) {
15219         blackPlaysFirst = TRUE;
15220         currentMove = 1;        /* kludge */
15221         DisplayBothClocks();
15222         currentMove = 0;
15223     } else if (gameMode == IcsExamining) {
15224         SendToICS(ics_prefix);
15225         SendToICS("tomove black\n");
15226     }
15227 }
15228
15229 void
15230 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15231 {
15232     char buf[MSG_SIZ];
15233     ChessSquare piece = boards[0][y][x];
15234     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15235     static int lastVariant;
15236
15237     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15238
15239     switch (selection) {
15240       case ClearBoard:
15241         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15242         MarkTargetSquares(1);
15243         CopyBoard(currentBoard, boards[0]);
15244         CopyBoard(menuBoard, initialPosition);
15245         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15246             SendToICS(ics_prefix);
15247             SendToICS("bsetup clear\n");
15248         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15249             SendToICS(ics_prefix);
15250             SendToICS("clearboard\n");
15251         } else {
15252             int nonEmpty = 0;
15253             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15254                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15255                 for (y = 0; y < BOARD_HEIGHT; y++) {
15256                     if (gameMode == IcsExamining) {
15257                         if (boards[currentMove][y][x] != EmptySquare) {
15258                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15259                                     AAA + x, ONE + y);
15260                             SendToICS(buf);
15261                         }
15262                     } else if(boards[0][y][x] != DarkSquare) {
15263                         if(boards[0][y][x] != p) nonEmpty++;
15264                         boards[0][y][x] = p;
15265                     }
15266                 }
15267             }
15268             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15269                 int r;
15270                 for(r = 0; r < BOARD_HEIGHT; r++) {
15271                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15272                     ChessSquare p = menuBoard[r][x];
15273                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15274                   }
15275                 }
15276                 DisplayMessage("Clicking clock again restores position", "");
15277                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15278                 if(!nonEmpty) { // asked to clear an empty board
15279                     CopyBoard(boards[0], menuBoard);
15280                 } else
15281                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15282                     CopyBoard(boards[0], initialPosition);
15283                 } else
15284                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15285                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15286                     CopyBoard(boards[0], erasedBoard);
15287                 } else
15288                     CopyBoard(erasedBoard, currentBoard);
15289
15290             }
15291         }
15292         if (gameMode == EditPosition) {
15293             DrawPosition(FALSE, boards[0]);
15294         }
15295         break;
15296
15297       case WhitePlay:
15298         SetWhiteToPlayEvent();
15299         break;
15300
15301       case BlackPlay:
15302         SetBlackToPlayEvent();
15303         break;
15304
15305       case EmptySquare:
15306         if (gameMode == IcsExamining) {
15307             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15308             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15309             SendToICS(buf);
15310         } else {
15311             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15312                 if(x == BOARD_LEFT-2) {
15313                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15314                     boards[0][y][1] = 0;
15315                 } else
15316                 if(x == BOARD_RGHT+1) {
15317                     if(y >= gameInfo.holdingsSize) break;
15318                     boards[0][y][BOARD_WIDTH-2] = 0;
15319                 } else break;
15320             }
15321             boards[0][y][x] = EmptySquare;
15322             DrawPosition(FALSE, boards[0]);
15323         }
15324         break;
15325
15326       case PromotePiece:
15327         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15328            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15329             selection = (ChessSquare) (PROMOTED piece);
15330         } else if(piece == EmptySquare) selection = WhiteSilver;
15331         else selection = (ChessSquare)((int)piece - 1);
15332         goto defaultlabel;
15333
15334       case DemotePiece:
15335         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15336            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15337             selection = (ChessSquare) (DEMOTED piece);
15338         } else if(piece == EmptySquare) selection = BlackSilver;
15339         else selection = (ChessSquare)((int)piece + 1);
15340         goto defaultlabel;
15341
15342       case WhiteQueen:
15343       case BlackQueen:
15344         if(gameInfo.variant == VariantShatranj ||
15345            gameInfo.variant == VariantXiangqi  ||
15346            gameInfo.variant == VariantCourier  ||
15347            gameInfo.variant == VariantASEAN    ||
15348            gameInfo.variant == VariantMakruk     )
15349             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15350         goto defaultlabel;
15351
15352       case WhiteKing:
15353       case BlackKing:
15354         if(gameInfo.variant == VariantXiangqi)
15355             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15356         if(gameInfo.variant == VariantKnightmate)
15357             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15358       default:
15359         defaultlabel:
15360         if (gameMode == IcsExamining) {
15361             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15362             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15363                      PieceToChar(selection), AAA + x, ONE + y);
15364             SendToICS(buf);
15365         } else {
15366             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15367                 int n;
15368                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15369                     n = PieceToNumber(selection - BlackPawn);
15370                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15371                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15372                     boards[0][BOARD_HEIGHT-1-n][1]++;
15373                 } else
15374                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15375                     n = PieceToNumber(selection);
15376                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15377                     boards[0][n][BOARD_WIDTH-1] = selection;
15378                     boards[0][n][BOARD_WIDTH-2]++;
15379                 }
15380             } else
15381             boards[0][y][x] = selection;
15382             DrawPosition(TRUE, boards[0]);
15383             ClearHighlights();
15384             fromX = fromY = -1;
15385         }
15386         break;
15387     }
15388 }
15389
15390
15391 void
15392 DropMenuEvent (ChessSquare selection, int x, int y)
15393 {
15394     ChessMove moveType;
15395
15396     switch (gameMode) {
15397       case IcsPlayingWhite:
15398       case MachinePlaysBlack:
15399         if (!WhiteOnMove(currentMove)) {
15400             DisplayMoveError(_("It is Black's turn"));
15401             return;
15402         }
15403         moveType = WhiteDrop;
15404         break;
15405       case IcsPlayingBlack:
15406       case MachinePlaysWhite:
15407         if (WhiteOnMove(currentMove)) {
15408             DisplayMoveError(_("It is White's turn"));
15409             return;
15410         }
15411         moveType = BlackDrop;
15412         break;
15413       case EditGame:
15414         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15415         break;
15416       default:
15417         return;
15418     }
15419
15420     if (moveType == BlackDrop && selection < BlackPawn) {
15421       selection = (ChessSquare) ((int) selection
15422                                  + (int) BlackPawn - (int) WhitePawn);
15423     }
15424     if (boards[currentMove][y][x] != EmptySquare) {
15425         DisplayMoveError(_("That square is occupied"));
15426         return;
15427     }
15428
15429     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15430 }
15431
15432 void
15433 AcceptEvent ()
15434 {
15435     /* Accept a pending offer of any kind from opponent */
15436
15437     if (appData.icsActive) {
15438         SendToICS(ics_prefix);
15439         SendToICS("accept\n");
15440     } else if (cmailMsgLoaded) {
15441         if (currentMove == cmailOldMove &&
15442             commentList[cmailOldMove] != NULL &&
15443             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15444                    "Black offers a draw" : "White offers a draw")) {
15445             TruncateGame();
15446             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15447             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15448         } else {
15449             DisplayError(_("There is no pending offer on this move"), 0);
15450             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15451         }
15452     } else {
15453         /* Not used for offers from chess program */
15454     }
15455 }
15456
15457 void
15458 DeclineEvent ()
15459 {
15460     /* Decline a pending offer of any kind from opponent */
15461
15462     if (appData.icsActive) {
15463         SendToICS(ics_prefix);
15464         SendToICS("decline\n");
15465     } else if (cmailMsgLoaded) {
15466         if (currentMove == cmailOldMove &&
15467             commentList[cmailOldMove] != NULL &&
15468             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15469                    "Black offers a draw" : "White offers a draw")) {
15470 #ifdef NOTDEF
15471             AppendComment(cmailOldMove, "Draw declined", TRUE);
15472             DisplayComment(cmailOldMove - 1, "Draw declined");
15473 #endif /*NOTDEF*/
15474         } else {
15475             DisplayError(_("There is no pending offer on this move"), 0);
15476         }
15477     } else {
15478         /* Not used for offers from chess program */
15479     }
15480 }
15481
15482 void
15483 RematchEvent ()
15484 {
15485     /* Issue ICS rematch command */
15486     if (appData.icsActive) {
15487         SendToICS(ics_prefix);
15488         SendToICS("rematch\n");
15489     }
15490 }
15491
15492 void
15493 CallFlagEvent ()
15494 {
15495     /* Call your opponent's flag (claim a win on time) */
15496     if (appData.icsActive) {
15497         SendToICS(ics_prefix);
15498         SendToICS("flag\n");
15499     } else {
15500         switch (gameMode) {
15501           default:
15502             return;
15503           case MachinePlaysWhite:
15504             if (whiteFlag) {
15505                 if (blackFlag)
15506                   GameEnds(GameIsDrawn, "Both players ran out of time",
15507                            GE_PLAYER);
15508                 else
15509                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15510             } else {
15511                 DisplayError(_("Your opponent is not out of time"), 0);
15512             }
15513             break;
15514           case MachinePlaysBlack:
15515             if (blackFlag) {
15516                 if (whiteFlag)
15517                   GameEnds(GameIsDrawn, "Both players ran out of time",
15518                            GE_PLAYER);
15519                 else
15520                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15521             } else {
15522                 DisplayError(_("Your opponent is not out of time"), 0);
15523             }
15524             break;
15525         }
15526     }
15527 }
15528
15529 void
15530 ClockClick (int which)
15531 {       // [HGM] code moved to back-end from winboard.c
15532         if(which) { // black clock
15533           if (gameMode == EditPosition || gameMode == IcsExamining) {
15534             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15535             SetBlackToPlayEvent();
15536           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15537                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15538           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15539           } else if (shiftKey) {
15540             AdjustClock(which, -1);
15541           } else if (gameMode == IcsPlayingWhite ||
15542                      gameMode == MachinePlaysBlack) {
15543             CallFlagEvent();
15544           }
15545         } else { // white clock
15546           if (gameMode == EditPosition || gameMode == IcsExamining) {
15547             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15548             SetWhiteToPlayEvent();
15549           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15550                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15551           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15552           } else if (shiftKey) {
15553             AdjustClock(which, -1);
15554           } else if (gameMode == IcsPlayingBlack ||
15555                    gameMode == MachinePlaysWhite) {
15556             CallFlagEvent();
15557           }
15558         }
15559 }
15560
15561 void
15562 DrawEvent ()
15563 {
15564     /* Offer draw or accept pending draw offer from opponent */
15565
15566     if (appData.icsActive) {
15567         /* Note: tournament rules require draw offers to be
15568            made after you make your move but before you punch
15569            your clock.  Currently ICS doesn't let you do that;
15570            instead, you immediately punch your clock after making
15571            a move, but you can offer a draw at any time. */
15572
15573         SendToICS(ics_prefix);
15574         SendToICS("draw\n");
15575         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15576     } else if (cmailMsgLoaded) {
15577         if (currentMove == cmailOldMove &&
15578             commentList[cmailOldMove] != NULL &&
15579             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15580                    "Black offers a draw" : "White offers a draw")) {
15581             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15582             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15583         } else if (currentMove == cmailOldMove + 1) {
15584             char *offer = WhiteOnMove(cmailOldMove) ?
15585               "White offers a draw" : "Black offers a draw";
15586             AppendComment(currentMove, offer, TRUE);
15587             DisplayComment(currentMove - 1, offer);
15588             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15589         } else {
15590             DisplayError(_("You must make your move before offering a draw"), 0);
15591             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15592         }
15593     } else if (first.offeredDraw) {
15594         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15595     } else {
15596         if (first.sendDrawOffers) {
15597             SendToProgram("draw\n", &first);
15598             userOfferedDraw = TRUE;
15599         }
15600     }
15601 }
15602
15603 void
15604 AdjournEvent ()
15605 {
15606     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15607
15608     if (appData.icsActive) {
15609         SendToICS(ics_prefix);
15610         SendToICS("adjourn\n");
15611     } else {
15612         /* Currently GNU Chess doesn't offer or accept Adjourns */
15613     }
15614 }
15615
15616
15617 void
15618 AbortEvent ()
15619 {
15620     /* Offer Abort or accept pending Abort offer from opponent */
15621
15622     if (appData.icsActive) {
15623         SendToICS(ics_prefix);
15624         SendToICS("abort\n");
15625     } else {
15626         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15627     }
15628 }
15629
15630 void
15631 ResignEvent ()
15632 {
15633     /* Resign.  You can do this even if it's not your turn. */
15634
15635     if (appData.icsActive) {
15636         SendToICS(ics_prefix);
15637         SendToICS("resign\n");
15638     } else {
15639         switch (gameMode) {
15640           case MachinePlaysWhite:
15641             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15642             break;
15643           case MachinePlaysBlack:
15644             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15645             break;
15646           case EditGame:
15647             if (cmailMsgLoaded) {
15648                 TruncateGame();
15649                 if (WhiteOnMove(cmailOldMove)) {
15650                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15651                 } else {
15652                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15653                 }
15654                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15655             }
15656             break;
15657           default:
15658             break;
15659         }
15660     }
15661 }
15662
15663
15664 void
15665 StopObservingEvent ()
15666 {
15667     /* Stop observing current games */
15668     SendToICS(ics_prefix);
15669     SendToICS("unobserve\n");
15670 }
15671
15672 void
15673 StopExaminingEvent ()
15674 {
15675     /* Stop observing current game */
15676     SendToICS(ics_prefix);
15677     SendToICS("unexamine\n");
15678 }
15679
15680 void
15681 ForwardInner (int target)
15682 {
15683     int limit; int oldSeekGraphUp = seekGraphUp;
15684
15685     if (appData.debugMode)
15686         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15687                 target, currentMove, forwardMostMove);
15688
15689     if (gameMode == EditPosition)
15690       return;
15691
15692     seekGraphUp = FALSE;
15693     MarkTargetSquares(1);
15694     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15695
15696     if (gameMode == PlayFromGameFile && !pausing)
15697       PauseEvent();
15698
15699     if (gameMode == IcsExamining && pausing)
15700       limit = pauseExamForwardMostMove;
15701     else
15702       limit = forwardMostMove;
15703
15704     if (target > limit) target = limit;
15705
15706     if (target > 0 && moveList[target - 1][0]) {
15707         int fromX, fromY, toX, toY;
15708         toX = moveList[target - 1][2] - AAA;
15709         toY = moveList[target - 1][3] - ONE;
15710         if (moveList[target - 1][1] == '@') {
15711             if (appData.highlightLastMove) {
15712                 SetHighlights(-1, -1, toX, toY);
15713             }
15714         } else {
15715             int viaX = moveList[target - 1][5] - AAA;
15716             int viaY = moveList[target - 1][6] - ONE;
15717             fromX = moveList[target - 1][0] - AAA;
15718             fromY = moveList[target - 1][1] - ONE;
15719             if (target == currentMove + 1) {
15720                 if(moveList[target - 1][4] == ';') { // multi-leg
15721                     ChessSquare piece = boards[currentMove][viaY][viaX];
15722                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15723                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15724                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15725                     boards[currentMove][viaY][viaX] = piece;
15726                 } else
15727                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15728             }
15729             if (appData.highlightLastMove) {
15730                 SetHighlights(fromX, fromY, toX, toY);
15731             }
15732         }
15733     }
15734     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15735         gameMode == Training || gameMode == PlayFromGameFile ||
15736         gameMode == AnalyzeFile) {
15737         while (currentMove < target) {
15738             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15739             SendMoveToProgram(currentMove++, &first);
15740         }
15741     } else {
15742         currentMove = target;
15743     }
15744
15745     if (gameMode == EditGame || gameMode == EndOfGame) {
15746         whiteTimeRemaining = timeRemaining[0][currentMove];
15747         blackTimeRemaining = timeRemaining[1][currentMove];
15748     }
15749     DisplayBothClocks();
15750     DisplayMove(currentMove - 1);
15751     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15752     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15753     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15754         DisplayComment(currentMove - 1, commentList[currentMove]);
15755     }
15756     ClearMap(); // [HGM] exclude: invalidate map
15757 }
15758
15759
15760 void
15761 ForwardEvent ()
15762 {
15763     if (gameMode == IcsExamining && !pausing) {
15764         SendToICS(ics_prefix);
15765         SendToICS("forward\n");
15766     } else {
15767         ForwardInner(currentMove + 1);
15768     }
15769 }
15770
15771 void
15772 ToEndEvent ()
15773 {
15774     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15775         /* to optimze, we temporarily turn off analysis mode while we feed
15776          * the remaining moves to the engine. Otherwise we get analysis output
15777          * after each move.
15778          */
15779         if (first.analysisSupport) {
15780           SendToProgram("exit\nforce\n", &first);
15781           first.analyzing = FALSE;
15782         }
15783     }
15784
15785     if (gameMode == IcsExamining && !pausing) {
15786         SendToICS(ics_prefix);
15787         SendToICS("forward 999999\n");
15788     } else {
15789         ForwardInner(forwardMostMove);
15790     }
15791
15792     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15793         /* we have fed all the moves, so reactivate analysis mode */
15794         SendToProgram("analyze\n", &first);
15795         first.analyzing = TRUE;
15796         /*first.maybeThinking = TRUE;*/
15797         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15798     }
15799 }
15800
15801 void
15802 BackwardInner (int target)
15803 {
15804     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15805
15806     if (appData.debugMode)
15807         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15808                 target, currentMove, forwardMostMove);
15809
15810     if (gameMode == EditPosition) return;
15811     seekGraphUp = FALSE;
15812     MarkTargetSquares(1);
15813     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15814     if (currentMove <= backwardMostMove) {
15815         ClearHighlights();
15816         DrawPosition(full_redraw, boards[currentMove]);
15817         return;
15818     }
15819     if (gameMode == PlayFromGameFile && !pausing)
15820       PauseEvent();
15821
15822     if (moveList[target][0]) {
15823         int fromX, fromY, toX, toY;
15824         toX = moveList[target][2] - AAA;
15825         toY = moveList[target][3] - ONE;
15826         if (moveList[target][1] == '@') {
15827             if (appData.highlightLastMove) {
15828                 SetHighlights(-1, -1, toX, toY);
15829             }
15830         } else {
15831             fromX = moveList[target][0] - AAA;
15832             fromY = moveList[target][1] - ONE;
15833             if (target == currentMove - 1) {
15834                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15835             }
15836             if (appData.highlightLastMove) {
15837                 SetHighlights(fromX, fromY, toX, toY);
15838             }
15839         }
15840     }
15841     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15842         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15843         while (currentMove > target) {
15844             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15845                 // null move cannot be undone. Reload program with move history before it.
15846                 int i;
15847                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15848                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15849                 }
15850                 SendBoard(&first, i);
15851               if(second.analyzing) SendBoard(&second, i);
15852                 for(currentMove=i; currentMove<target; currentMove++) {
15853                     SendMoveToProgram(currentMove, &first);
15854                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15855                 }
15856                 break;
15857             }
15858             SendToBoth("undo\n");
15859             currentMove--;
15860         }
15861     } else {
15862         currentMove = target;
15863     }
15864
15865     if (gameMode == EditGame || gameMode == EndOfGame) {
15866         whiteTimeRemaining = timeRemaining[0][currentMove];
15867         blackTimeRemaining = timeRemaining[1][currentMove];
15868     }
15869     DisplayBothClocks();
15870     DisplayMove(currentMove - 1);
15871     DrawPosition(full_redraw, boards[currentMove]);
15872     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15873     // [HGM] PV info: routine tests if comment empty
15874     DisplayComment(currentMove - 1, commentList[currentMove]);
15875     ClearMap(); // [HGM] exclude: invalidate map
15876 }
15877
15878 void
15879 BackwardEvent ()
15880 {
15881     if (gameMode == IcsExamining && !pausing) {
15882         SendToICS(ics_prefix);
15883         SendToICS("backward\n");
15884     } else {
15885         BackwardInner(currentMove - 1);
15886     }
15887 }
15888
15889 void
15890 ToStartEvent ()
15891 {
15892     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15893         /* to optimize, we temporarily turn off analysis mode while we undo
15894          * all the moves. Otherwise we get analysis output after each undo.
15895          */
15896         if (first.analysisSupport) {
15897           SendToProgram("exit\nforce\n", &first);
15898           first.analyzing = FALSE;
15899         }
15900     }
15901
15902     if (gameMode == IcsExamining && !pausing) {
15903         SendToICS(ics_prefix);
15904         SendToICS("backward 999999\n");
15905     } else {
15906         BackwardInner(backwardMostMove);
15907     }
15908
15909     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15910         /* we have fed all the moves, so reactivate analysis mode */
15911         SendToProgram("analyze\n", &first);
15912         first.analyzing = TRUE;
15913         /*first.maybeThinking = TRUE;*/
15914         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15915     }
15916 }
15917
15918 void
15919 ToNrEvent (int to)
15920 {
15921   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15922   if (to >= forwardMostMove) to = forwardMostMove;
15923   if (to <= backwardMostMove) to = backwardMostMove;
15924   if (to < currentMove) {
15925     BackwardInner(to);
15926   } else {
15927     ForwardInner(to);
15928   }
15929 }
15930
15931 void
15932 RevertEvent (Boolean annotate)
15933 {
15934     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15935         return;
15936     }
15937     if (gameMode != IcsExamining) {
15938         DisplayError(_("You are not examining a game"), 0);
15939         return;
15940     }
15941     if (pausing) {
15942         DisplayError(_("You can't revert while pausing"), 0);
15943         return;
15944     }
15945     SendToICS(ics_prefix);
15946     SendToICS("revert\n");
15947 }
15948
15949 void
15950 RetractMoveEvent ()
15951 {
15952     switch (gameMode) {
15953       case MachinePlaysWhite:
15954       case MachinePlaysBlack:
15955         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15956             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15957             return;
15958         }
15959         if (forwardMostMove < 2) return;
15960         currentMove = forwardMostMove = forwardMostMove - 2;
15961         whiteTimeRemaining = timeRemaining[0][currentMove];
15962         blackTimeRemaining = timeRemaining[1][currentMove];
15963         DisplayBothClocks();
15964         DisplayMove(currentMove - 1);
15965         ClearHighlights();/*!! could figure this out*/
15966         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15967         SendToProgram("remove\n", &first);
15968         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15969         break;
15970
15971       case BeginningOfGame:
15972       default:
15973         break;
15974
15975       case IcsPlayingWhite:
15976       case IcsPlayingBlack:
15977         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15978             SendToICS(ics_prefix);
15979             SendToICS("takeback 2\n");
15980         } else {
15981             SendToICS(ics_prefix);
15982             SendToICS("takeback 1\n");
15983         }
15984         break;
15985     }
15986 }
15987
15988 void
15989 MoveNowEvent ()
15990 {
15991     ChessProgramState *cps;
15992
15993     switch (gameMode) {
15994       case MachinePlaysWhite:
15995         if (!WhiteOnMove(forwardMostMove)) {
15996             DisplayError(_("It is your turn"), 0);
15997             return;
15998         }
15999         cps = &first;
16000         break;
16001       case MachinePlaysBlack:
16002         if (WhiteOnMove(forwardMostMove)) {
16003             DisplayError(_("It is your turn"), 0);
16004             return;
16005         }
16006         cps = &first;
16007         break;
16008       case TwoMachinesPlay:
16009         if (WhiteOnMove(forwardMostMove) ==
16010             (first.twoMachinesColor[0] == 'w')) {
16011             cps = &first;
16012         } else {
16013             cps = &second;
16014         }
16015         break;
16016       case BeginningOfGame:
16017       default:
16018         return;
16019     }
16020     SendToProgram("?\n", cps);
16021 }
16022
16023 void
16024 TruncateGameEvent ()
16025 {
16026     EditGameEvent();
16027     if (gameMode != EditGame) return;
16028     TruncateGame();
16029 }
16030
16031 void
16032 TruncateGame ()
16033 {
16034     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16035     if (forwardMostMove > currentMove) {
16036         if (gameInfo.resultDetails != NULL) {
16037             free(gameInfo.resultDetails);
16038             gameInfo.resultDetails = NULL;
16039             gameInfo.result = GameUnfinished;
16040         }
16041         forwardMostMove = currentMove;
16042         HistorySet(parseList, backwardMostMove, forwardMostMove,
16043                    currentMove-1);
16044     }
16045 }
16046
16047 void
16048 HintEvent ()
16049 {
16050     if (appData.noChessProgram) return;
16051     switch (gameMode) {
16052       case MachinePlaysWhite:
16053         if (WhiteOnMove(forwardMostMove)) {
16054             DisplayError(_("Wait until your turn."), 0);
16055             return;
16056         }
16057         break;
16058       case BeginningOfGame:
16059       case MachinePlaysBlack:
16060         if (!WhiteOnMove(forwardMostMove)) {
16061             DisplayError(_("Wait until your turn."), 0);
16062             return;
16063         }
16064         break;
16065       default:
16066         DisplayError(_("No hint available"), 0);
16067         return;
16068     }
16069     SendToProgram("hint\n", &first);
16070     hintRequested = TRUE;
16071 }
16072
16073 int
16074 SaveSelected (FILE *g, int dummy, char *dummy2)
16075 {
16076     ListGame * lg = (ListGame *) gameList.head;
16077     int nItem, cnt=0;
16078     FILE *f;
16079
16080     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16081         DisplayError(_("Game list not loaded or empty"), 0);
16082         return 0;
16083     }
16084
16085     creatingBook = TRUE; // suppresses stuff during load game
16086
16087     /* Get list size */
16088     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16089         if(lg->position >= 0) { // selected?
16090             LoadGame(f, nItem, "", TRUE);
16091             SaveGamePGN2(g); // leaves g open
16092             cnt++; DoEvents();
16093         }
16094         lg = (ListGame *) lg->node.succ;
16095     }
16096
16097     fclose(g);
16098     creatingBook = FALSE;
16099
16100     return cnt;
16101 }
16102
16103 void
16104 CreateBookEvent ()
16105 {
16106     ListGame * lg = (ListGame *) gameList.head;
16107     FILE *f, *g;
16108     int nItem;
16109     static int secondTime = FALSE;
16110
16111     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16112         DisplayError(_("Game list not loaded or empty"), 0);
16113         return;
16114     }
16115
16116     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16117         fclose(g);
16118         secondTime++;
16119         DisplayNote(_("Book file exists! Try again for overwrite."));
16120         return;
16121     }
16122
16123     creatingBook = TRUE;
16124     secondTime = FALSE;
16125
16126     /* Get list size */
16127     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16128         if(lg->position >= 0) {
16129             LoadGame(f, nItem, "", TRUE);
16130             AddGameToBook(TRUE);
16131             DoEvents();
16132         }
16133         lg = (ListGame *) lg->node.succ;
16134     }
16135
16136     creatingBook = FALSE;
16137     FlushBook();
16138 }
16139
16140 void
16141 BookEvent ()
16142 {
16143     if (appData.noChessProgram) return;
16144     switch (gameMode) {
16145       case MachinePlaysWhite:
16146         if (WhiteOnMove(forwardMostMove)) {
16147             DisplayError(_("Wait until your turn."), 0);
16148             return;
16149         }
16150         break;
16151       case BeginningOfGame:
16152       case MachinePlaysBlack:
16153         if (!WhiteOnMove(forwardMostMove)) {
16154             DisplayError(_("Wait until your turn."), 0);
16155             return;
16156         }
16157         break;
16158       case EditPosition:
16159         EditPositionDone(TRUE);
16160         break;
16161       case TwoMachinesPlay:
16162         return;
16163       default:
16164         break;
16165     }
16166     SendToProgram("bk\n", &first);
16167     bookOutput[0] = NULLCHAR;
16168     bookRequested = TRUE;
16169 }
16170
16171 void
16172 AboutGameEvent ()
16173 {
16174     char *tags = PGNTags(&gameInfo);
16175     TagsPopUp(tags, CmailMsg());
16176     free(tags);
16177 }
16178
16179 /* end button procedures */
16180
16181 void
16182 PrintPosition (FILE *fp, int move)
16183 {
16184     int i, j;
16185
16186     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16187         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16188             char c = PieceToChar(boards[move][i][j]);
16189             fputc(c == 'x' ? '.' : c, fp);
16190             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16191         }
16192     }
16193     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16194       fprintf(fp, "white to play\n");
16195     else
16196       fprintf(fp, "black to play\n");
16197 }
16198
16199 void
16200 PrintOpponents (FILE *fp)
16201 {
16202     if (gameInfo.white != NULL) {
16203         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16204     } else {
16205         fprintf(fp, "\n");
16206     }
16207 }
16208
16209 /* Find last component of program's own name, using some heuristics */
16210 void
16211 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16212 {
16213     char *p, *q, c;
16214     int local = (strcmp(host, "localhost") == 0);
16215     while (!local && (p = strchr(prog, ';')) != NULL) {
16216         p++;
16217         while (*p == ' ') p++;
16218         prog = p;
16219     }
16220     if (*prog == '"' || *prog == '\'') {
16221         q = strchr(prog + 1, *prog);
16222     } else {
16223         q = strchr(prog, ' ');
16224     }
16225     if (q == NULL) q = prog + strlen(prog);
16226     p = q;
16227     while (p >= prog && *p != '/' && *p != '\\') p--;
16228     p++;
16229     if(p == prog && *p == '"') p++;
16230     c = *q; *q = 0;
16231     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16232     memcpy(buf, p, q - p);
16233     buf[q - p] = NULLCHAR;
16234     if (!local) {
16235         strcat(buf, "@");
16236         strcat(buf, host);
16237     }
16238 }
16239
16240 char *
16241 TimeControlTagValue ()
16242 {
16243     char buf[MSG_SIZ];
16244     if (!appData.clockMode) {
16245       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16246     } else if (movesPerSession > 0) {
16247       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16248     } else if (timeIncrement == 0) {
16249       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16250     } else {
16251       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16252     }
16253     return StrSave(buf);
16254 }
16255
16256 void
16257 SetGameInfo ()
16258 {
16259     /* This routine is used only for certain modes */
16260     VariantClass v = gameInfo.variant;
16261     ChessMove r = GameUnfinished;
16262     char *p = NULL;
16263
16264     if(keepInfo) return;
16265
16266     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16267         r = gameInfo.result;
16268         p = gameInfo.resultDetails;
16269         gameInfo.resultDetails = NULL;
16270     }
16271     ClearGameInfo(&gameInfo);
16272     gameInfo.variant = v;
16273
16274     switch (gameMode) {
16275       case MachinePlaysWhite:
16276         gameInfo.event = StrSave( appData.pgnEventHeader );
16277         gameInfo.site = StrSave(HostName());
16278         gameInfo.date = PGNDate();
16279         gameInfo.round = StrSave("-");
16280         gameInfo.white = StrSave(first.tidy);
16281         gameInfo.black = StrSave(UserName());
16282         gameInfo.timeControl = TimeControlTagValue();
16283         break;
16284
16285       case MachinePlaysBlack:
16286         gameInfo.event = StrSave( appData.pgnEventHeader );
16287         gameInfo.site = StrSave(HostName());
16288         gameInfo.date = PGNDate();
16289         gameInfo.round = StrSave("-");
16290         gameInfo.white = StrSave(UserName());
16291         gameInfo.black = StrSave(first.tidy);
16292         gameInfo.timeControl = TimeControlTagValue();
16293         break;
16294
16295       case TwoMachinesPlay:
16296         gameInfo.event = StrSave( appData.pgnEventHeader );
16297         gameInfo.site = StrSave(HostName());
16298         gameInfo.date = PGNDate();
16299         if (roundNr > 0) {
16300             char buf[MSG_SIZ];
16301             snprintf(buf, MSG_SIZ, "%d", roundNr);
16302             gameInfo.round = StrSave(buf);
16303         } else {
16304             gameInfo.round = StrSave("-");
16305         }
16306         if (first.twoMachinesColor[0] == 'w') {
16307             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16308             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16309         } else {
16310             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16311             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16312         }
16313         gameInfo.timeControl = TimeControlTagValue();
16314         break;
16315
16316       case EditGame:
16317         gameInfo.event = StrSave("Edited game");
16318         gameInfo.site = StrSave(HostName());
16319         gameInfo.date = PGNDate();
16320         gameInfo.round = StrSave("-");
16321         gameInfo.white = StrSave("-");
16322         gameInfo.black = StrSave("-");
16323         gameInfo.result = r;
16324         gameInfo.resultDetails = p;
16325         break;
16326
16327       case EditPosition:
16328         gameInfo.event = StrSave("Edited position");
16329         gameInfo.site = StrSave(HostName());
16330         gameInfo.date = PGNDate();
16331         gameInfo.round = StrSave("-");
16332         gameInfo.white = StrSave("-");
16333         gameInfo.black = StrSave("-");
16334         break;
16335
16336       case IcsPlayingWhite:
16337       case IcsPlayingBlack:
16338       case IcsObserving:
16339       case IcsExamining:
16340         break;
16341
16342       case PlayFromGameFile:
16343         gameInfo.event = StrSave("Game from non-PGN file");
16344         gameInfo.site = StrSave(HostName());
16345         gameInfo.date = PGNDate();
16346         gameInfo.round = StrSave("-");
16347         gameInfo.white = StrSave("?");
16348         gameInfo.black = StrSave("?");
16349         break;
16350
16351       default:
16352         break;
16353     }
16354 }
16355
16356 void
16357 ReplaceComment (int index, char *text)
16358 {
16359     int len;
16360     char *p;
16361     float score;
16362
16363     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16364        pvInfoList[index-1].depth == len &&
16365        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16366        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16367     while (*text == '\n') text++;
16368     len = strlen(text);
16369     while (len > 0 && text[len - 1] == '\n') len--;
16370
16371     if (commentList[index] != NULL)
16372       free(commentList[index]);
16373
16374     if (len == 0) {
16375         commentList[index] = NULL;
16376         return;
16377     }
16378   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16379       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16380       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16381     commentList[index] = (char *) malloc(len + 2);
16382     strncpy(commentList[index], text, len);
16383     commentList[index][len] = '\n';
16384     commentList[index][len + 1] = NULLCHAR;
16385   } else {
16386     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16387     char *p;
16388     commentList[index] = (char *) malloc(len + 7);
16389     safeStrCpy(commentList[index], "{\n", 3);
16390     safeStrCpy(commentList[index]+2, text, len+1);
16391     commentList[index][len+2] = NULLCHAR;
16392     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16393     strcat(commentList[index], "\n}\n");
16394   }
16395 }
16396
16397 void
16398 CrushCRs (char *text)
16399 {
16400   char *p = text;
16401   char *q = text;
16402   char ch;
16403
16404   do {
16405     ch = *p++;
16406     if (ch == '\r') continue;
16407     *q++ = ch;
16408   } while (ch != '\0');
16409 }
16410
16411 void
16412 AppendComment (int index, char *text, Boolean addBraces)
16413 /* addBraces  tells if we should add {} */
16414 {
16415     int oldlen, len;
16416     char *old;
16417
16418 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16419     if(addBraces == 3) addBraces = 0; else // force appending literally
16420     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16421
16422     CrushCRs(text);
16423     while (*text == '\n') text++;
16424     len = strlen(text);
16425     while (len > 0 && text[len - 1] == '\n') len--;
16426     text[len] = NULLCHAR;
16427
16428     if (len == 0) return;
16429
16430     if (commentList[index] != NULL) {
16431       Boolean addClosingBrace = addBraces;
16432         old = commentList[index];
16433         oldlen = strlen(old);
16434         while(commentList[index][oldlen-1] ==  '\n')
16435           commentList[index][--oldlen] = NULLCHAR;
16436         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16437         safeStrCpy(commentList[index], old, oldlen + len + 6);
16438         free(old);
16439         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16440         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16441           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16442           while (*text == '\n') { text++; len--; }
16443           commentList[index][--oldlen] = NULLCHAR;
16444       }
16445         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16446         else          strcat(commentList[index], "\n");
16447         strcat(commentList[index], text);
16448         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16449         else          strcat(commentList[index], "\n");
16450     } else {
16451         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16452         if(addBraces)
16453           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16454         else commentList[index][0] = NULLCHAR;
16455         strcat(commentList[index], text);
16456         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16457         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16458     }
16459 }
16460
16461 static char *
16462 FindStr (char * text, char * sub_text)
16463 {
16464     char * result = strstr( text, sub_text );
16465
16466     if( result != NULL ) {
16467         result += strlen( sub_text );
16468     }
16469
16470     return result;
16471 }
16472
16473 /* [AS] Try to extract PV info from PGN comment */
16474 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16475 char *
16476 GetInfoFromComment (int index, char * text)
16477 {
16478     char * sep = text, *p;
16479
16480     if( text != NULL && index > 0 ) {
16481         int score = 0;
16482         int depth = 0;
16483         int time = -1, sec = 0, deci;
16484         char * s_eval = FindStr( text, "[%eval " );
16485         char * s_emt = FindStr( text, "[%emt " );
16486 #if 0
16487         if( s_eval != NULL || s_emt != NULL ) {
16488 #else
16489         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16490 #endif
16491             /* New style */
16492             char delim;
16493
16494             if( s_eval != NULL ) {
16495                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16496                     return text;
16497                 }
16498
16499                 if( delim != ']' ) {
16500                     return text;
16501                 }
16502             }
16503
16504             if( s_emt != NULL ) {
16505             }
16506                 return text;
16507         }
16508         else {
16509             /* We expect something like: [+|-]nnn.nn/dd */
16510             int score_lo = 0;
16511
16512             if(*text != '{') return text; // [HGM] braces: must be normal comment
16513
16514             sep = strchr( text, '/' );
16515             if( sep == NULL || sep < (text+4) ) {
16516                 return text;
16517             }
16518
16519             p = text;
16520             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16521             if(p[1] == '(') { // comment starts with PV
16522                p = strchr(p, ')'); // locate end of PV
16523                if(p == NULL || sep < p+5) return text;
16524                // at this point we have something like "{(.*) +0.23/6 ..."
16525                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16526                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16527                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16528             }
16529             time = -1; sec = -1; deci = -1;
16530             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16531                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16532                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16533                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16534                 return text;
16535             }
16536
16537             if( score_lo < 0 || score_lo >= 100 ) {
16538                 return text;
16539             }
16540
16541             if(sec >= 0) time = 600*time + 10*sec; else
16542             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16543
16544             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16545
16546             /* [HGM] PV time: now locate end of PV info */
16547             while( *++sep >= '0' && *sep <= '9'); // strip depth
16548             if(time >= 0)
16549             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16550             if(sec >= 0)
16551             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16552             if(deci >= 0)
16553             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16554             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16555         }
16556
16557         if( depth <= 0 ) {
16558             return text;
16559         }
16560
16561         if( time < 0 ) {
16562             time = -1;
16563         }
16564
16565         pvInfoList[index-1].depth = depth;
16566         pvInfoList[index-1].score = score;
16567         pvInfoList[index-1].time  = 10*time; // centi-sec
16568         if(*sep == '}') *sep = 0; else *--sep = '{';
16569         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16570     }
16571     return sep;
16572 }
16573
16574 void
16575 SendToProgram (char *message, ChessProgramState *cps)
16576 {
16577     int count, outCount, error;
16578     char buf[MSG_SIZ];
16579
16580     if (cps->pr == NoProc) return;
16581     Attention(cps);
16582
16583     if (appData.debugMode) {
16584         TimeMark now;
16585         GetTimeMark(&now);
16586         fprintf(debugFP, "%ld >%-6s: %s",
16587                 SubtractTimeMarks(&now, &programStartTime),
16588                 cps->which, message);
16589         if(serverFP)
16590             fprintf(serverFP, "%ld >%-6s: %s",
16591                 SubtractTimeMarks(&now, &programStartTime),
16592                 cps->which, message), fflush(serverFP);
16593     }
16594
16595     count = strlen(message);
16596     outCount = OutputToProcess(cps->pr, message, count, &error);
16597     if (outCount < count && !exiting
16598                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16599       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16600       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16601         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16602             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16603                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16604                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16605                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16606             } else {
16607                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16608                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16609                 gameInfo.result = res;
16610             }
16611             gameInfo.resultDetails = StrSave(buf);
16612         }
16613         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16614         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16615     }
16616 }
16617
16618 void
16619 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16620 {
16621     char *end_str;
16622     char buf[MSG_SIZ];
16623     ChessProgramState *cps = (ChessProgramState *)closure;
16624
16625     if (isr != cps->isr) return; /* Killed intentionally */
16626     if (count <= 0) {
16627         if (count == 0) {
16628             RemoveInputSource(cps->isr);
16629             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16630                     _(cps->which), cps->program);
16631             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16632             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16633                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16634                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16635                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16636                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16637                 } else {
16638                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16639                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16640                     gameInfo.result = res;
16641                 }
16642                 gameInfo.resultDetails = StrSave(buf);
16643             }
16644             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16645             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16646         } else {
16647             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16648                     _(cps->which), cps->program);
16649             RemoveInputSource(cps->isr);
16650
16651             /* [AS] Program is misbehaving badly... kill it */
16652             if( count == -2 ) {
16653                 DestroyChildProcess( cps->pr, 9 );
16654                 cps->pr = NoProc;
16655             }
16656
16657             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16658         }
16659         return;
16660     }
16661
16662     if ((end_str = strchr(message, '\r')) != NULL)
16663       *end_str = NULLCHAR;
16664     if ((end_str = strchr(message, '\n')) != NULL)
16665       *end_str = NULLCHAR;
16666
16667     if (appData.debugMode) {
16668         TimeMark now; int print = 1;
16669         char *quote = ""; char c; int i;
16670
16671         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16672                 char start = message[0];
16673                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16674                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16675                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16676                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16677                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16678                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16679                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16680                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16681                    sscanf(message, "hint: %c", &c)!=1 &&
16682                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16683                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16684                     print = (appData.engineComments >= 2);
16685                 }
16686                 message[0] = start; // restore original message
16687         }
16688         if(print) {
16689                 GetTimeMark(&now);
16690                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16691                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16692                         quote,
16693                         message);
16694                 if(serverFP)
16695                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16696                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16697                         quote,
16698                         message), fflush(serverFP);
16699         }
16700     }
16701
16702     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16703     if (appData.icsEngineAnalyze) {
16704         if (strstr(message, "whisper") != NULL ||
16705              strstr(message, "kibitz") != NULL ||
16706             strstr(message, "tellics") != NULL) return;
16707     }
16708
16709     HandleMachineMove(message, cps);
16710 }
16711
16712
16713 void
16714 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16715 {
16716     char buf[MSG_SIZ];
16717     int seconds;
16718
16719     if( timeControl_2 > 0 ) {
16720         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16721             tc = timeControl_2;
16722         }
16723     }
16724     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16725     inc /= cps->timeOdds;
16726     st  /= cps->timeOdds;
16727
16728     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16729
16730     if (st > 0) {
16731       /* Set exact time per move, normally using st command */
16732       if (cps->stKludge) {
16733         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16734         seconds = st % 60;
16735         if (seconds == 0) {
16736           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16737         } else {
16738           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16739         }
16740       } else {
16741         snprintf(buf, MSG_SIZ, "st %d\n", st);
16742       }
16743     } else {
16744       /* Set conventional or incremental time control, using level command */
16745       if (seconds == 0) {
16746         /* Note old gnuchess bug -- minutes:seconds used to not work.
16747            Fixed in later versions, but still avoid :seconds
16748            when seconds is 0. */
16749         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16750       } else {
16751         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16752                  seconds, inc/1000.);
16753       }
16754     }
16755     SendToProgram(buf, cps);
16756
16757     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16758     /* Orthogonally, limit search to given depth */
16759     if (sd > 0) {
16760       if (cps->sdKludge) {
16761         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16762       } else {
16763         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16764       }
16765       SendToProgram(buf, cps);
16766     }
16767
16768     if(cps->nps >= 0) { /* [HGM] nps */
16769         if(cps->supportsNPS == FALSE)
16770           cps->nps = -1; // don't use if engine explicitly says not supported!
16771         else {
16772           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16773           SendToProgram(buf, cps);
16774         }
16775     }
16776 }
16777
16778 ChessProgramState *
16779 WhitePlayer ()
16780 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16781 {
16782     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16783        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16784         return &second;
16785     return &first;
16786 }
16787
16788 void
16789 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16790 {
16791     char message[MSG_SIZ];
16792     long time, otime;
16793
16794     /* Note: this routine must be called when the clocks are stopped
16795        or when they have *just* been set or switched; otherwise
16796        it will be off by the time since the current tick started.
16797     */
16798     if (machineWhite) {
16799         time = whiteTimeRemaining / 10;
16800         otime = blackTimeRemaining / 10;
16801     } else {
16802         time = blackTimeRemaining / 10;
16803         otime = whiteTimeRemaining / 10;
16804     }
16805     /* [HGM] translate opponent's time by time-odds factor */
16806     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16807
16808     if (time <= 0) time = 1;
16809     if (otime <= 0) otime = 1;
16810
16811     snprintf(message, MSG_SIZ, "time %ld\n", time);
16812     SendToProgram(message, cps);
16813
16814     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16815     SendToProgram(message, cps);
16816 }
16817
16818 char *
16819 EngineDefinedVariant (ChessProgramState *cps, int n)
16820 {   // return name of n-th unknown variant that engine supports
16821     static char buf[MSG_SIZ];
16822     char *p, *s = cps->variants;
16823     if(!s) return NULL;
16824     do { // parse string from variants feature
16825       VariantClass v;
16826         p = strchr(s, ',');
16827         if(p) *p = NULLCHAR;
16828       v = StringToVariant(s);
16829       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16830         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16831             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16832                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16833                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16834                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16835             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16836         }
16837         if(p) *p++ = ',';
16838         if(n < 0) return buf;
16839     } while(s = p);
16840     return NULL;
16841 }
16842
16843 int
16844 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16845 {
16846   char buf[MSG_SIZ];
16847   int len = strlen(name);
16848   int val;
16849
16850   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16851     (*p) += len + 1;
16852     sscanf(*p, "%d", &val);
16853     *loc = (val != 0);
16854     while (**p && **p != ' ')
16855       (*p)++;
16856     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16857     SendToProgram(buf, cps);
16858     return TRUE;
16859   }
16860   return FALSE;
16861 }
16862
16863 int
16864 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16865 {
16866   char buf[MSG_SIZ];
16867   int len = strlen(name);
16868   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16869     (*p) += len + 1;
16870     sscanf(*p, "%d", loc);
16871     while (**p && **p != ' ') (*p)++;
16872     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16873     SendToProgram(buf, cps);
16874     return TRUE;
16875   }
16876   return FALSE;
16877 }
16878
16879 int
16880 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16881 {
16882   char buf[MSG_SIZ];
16883   int len = strlen(name);
16884   if (strncmp((*p), name, len) == 0
16885       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16886     (*p) += len + 2;
16887     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16888     sscanf(*p, "%[^\"]", *loc);
16889     while (**p && **p != '\"') (*p)++;
16890     if (**p == '\"') (*p)++;
16891     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16892     SendToProgram(buf, cps);
16893     return TRUE;
16894   }
16895   return FALSE;
16896 }
16897
16898 int
16899 ParseOption (Option *opt, ChessProgramState *cps)
16900 // [HGM] options: process the string that defines an engine option, and determine
16901 // name, type, default value, and allowed value range
16902 {
16903         char *p, *q, buf[MSG_SIZ];
16904         int n, min = (-1)<<31, max = 1<<31, def;
16905
16906         if(p = strstr(opt->name, " -spin ")) {
16907             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16908             if(max < min) max = min; // enforce consistency
16909             if(def < min) def = min;
16910             if(def > max) def = max;
16911             opt->value = def;
16912             opt->min = min;
16913             opt->max = max;
16914             opt->type = Spin;
16915         } else if((p = strstr(opt->name, " -slider "))) {
16916             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16917             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16918             if(max < min) max = min; // enforce consistency
16919             if(def < min) def = min;
16920             if(def > max) def = max;
16921             opt->value = def;
16922             opt->min = min;
16923             opt->max = max;
16924             opt->type = Spin; // Slider;
16925         } else if((p = strstr(opt->name, " -string "))) {
16926             opt->textValue = p+9;
16927             opt->type = TextBox;
16928         } else if((p = strstr(opt->name, " -file "))) {
16929             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16930             opt->textValue = p+7;
16931             opt->type = FileName; // FileName;
16932         } else if((p = strstr(opt->name, " -path "))) {
16933             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16934             opt->textValue = p+7;
16935             opt->type = PathName; // PathName;
16936         } else if(p = strstr(opt->name, " -check ")) {
16937             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16938             opt->value = (def != 0);
16939             opt->type = CheckBox;
16940         } else if(p = strstr(opt->name, " -combo ")) {
16941             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16942             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16943             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16944             opt->value = n = 0;
16945             while(q = StrStr(q, " /// ")) {
16946                 n++; *q = 0;    // count choices, and null-terminate each of them
16947                 q += 5;
16948                 if(*q == '*') { // remember default, which is marked with * prefix
16949                     q++;
16950                     opt->value = n;
16951                 }
16952                 cps->comboList[cps->comboCnt++] = q;
16953             }
16954             cps->comboList[cps->comboCnt++] = NULL;
16955             opt->max = n + 1;
16956             opt->type = ComboBox;
16957         } else if(p = strstr(opt->name, " -button")) {
16958             opt->type = Button;
16959         } else if(p = strstr(opt->name, " -save")) {
16960             opt->type = SaveButton;
16961         } else return FALSE;
16962         *p = 0; // terminate option name
16963         // now look if the command-line options define a setting for this engine option.
16964         if(cps->optionSettings && cps->optionSettings[0])
16965             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16966         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16967           snprintf(buf, MSG_SIZ, "option %s", p);
16968                 if(p = strstr(buf, ",")) *p = 0;
16969                 if(q = strchr(buf, '=')) switch(opt->type) {
16970                     case ComboBox:
16971                         for(n=0; n<opt->max; n++)
16972                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16973                         break;
16974                     case TextBox:
16975                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16976                         break;
16977                     case Spin:
16978                     case CheckBox:
16979                         opt->value = atoi(q+1);
16980                     default:
16981                         break;
16982                 }
16983                 strcat(buf, "\n");
16984                 SendToProgram(buf, cps);
16985         }
16986         return TRUE;
16987 }
16988
16989 void
16990 FeatureDone (ChessProgramState *cps, int val)
16991 {
16992   DelayedEventCallback cb = GetDelayedEvent();
16993   if ((cb == InitBackEnd3 && cps == &first) ||
16994       (cb == SettingsMenuIfReady && cps == &second) ||
16995       (cb == LoadEngine) ||
16996       (cb == TwoMachinesEventIfReady)) {
16997     CancelDelayedEvent();
16998     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16999   }
17000   cps->initDone = val;
17001   if(val) cps->reload = FALSE;
17002 }
17003
17004 /* Parse feature command from engine */
17005 void
17006 ParseFeatures (char *args, ChessProgramState *cps)
17007 {
17008   char *p = args;
17009   char *q = NULL;
17010   int val;
17011   char buf[MSG_SIZ];
17012
17013   for (;;) {
17014     while (*p == ' ') p++;
17015     if (*p == NULLCHAR) return;
17016
17017     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17018     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17019     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17020     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17021     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17022     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17023     if (BoolFeature(&p, "reuse", &val, cps)) {
17024       /* Engine can disable reuse, but can't enable it if user said no */
17025       if (!val) cps->reuse = FALSE;
17026       continue;
17027     }
17028     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17029     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17030       if (gameMode == TwoMachinesPlay) {
17031         DisplayTwoMachinesTitle();
17032       } else {
17033         DisplayTitle("");
17034       }
17035       continue;
17036     }
17037     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17038     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17039     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17040     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17041     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17042     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17043     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17044     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17045     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17046     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17047     if (IntFeature(&p, "done", &val, cps)) {
17048       FeatureDone(cps, val);
17049       continue;
17050     }
17051     /* Added by Tord: */
17052     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17053     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17054     /* End of additions by Tord */
17055
17056     /* [HGM] added features: */
17057     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17058     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17059     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17060     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17061     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17062     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17063     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17064     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17065         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17066         FREE(cps->option[cps->nrOptions].name);
17067         cps->option[cps->nrOptions].name = q; q = NULL;
17068         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17069           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17070             SendToProgram(buf, cps);
17071             continue;
17072         }
17073         if(cps->nrOptions >= MAX_OPTIONS) {
17074             cps->nrOptions--;
17075             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17076             DisplayError(buf, 0);
17077         }
17078         continue;
17079     }
17080     /* End of additions by HGM */
17081
17082     /* unknown feature: complain and skip */
17083     q = p;
17084     while (*q && *q != '=') q++;
17085     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17086     SendToProgram(buf, cps);
17087     p = q;
17088     if (*p == '=') {
17089       p++;
17090       if (*p == '\"') {
17091         p++;
17092         while (*p && *p != '\"') p++;
17093         if (*p == '\"') p++;
17094       } else {
17095         while (*p && *p != ' ') p++;
17096       }
17097     }
17098   }
17099
17100 }
17101
17102 void
17103 PeriodicUpdatesEvent (int newState)
17104 {
17105     if (newState == appData.periodicUpdates)
17106       return;
17107
17108     appData.periodicUpdates=newState;
17109
17110     /* Display type changes, so update it now */
17111 //    DisplayAnalysis();
17112
17113     /* Get the ball rolling again... */
17114     if (newState) {
17115         AnalysisPeriodicEvent(1);
17116         StartAnalysisClock();
17117     }
17118 }
17119
17120 void
17121 PonderNextMoveEvent (int newState)
17122 {
17123     if (newState == appData.ponderNextMove) return;
17124     if (gameMode == EditPosition) EditPositionDone(TRUE);
17125     if (newState) {
17126         SendToProgram("hard\n", &first);
17127         if (gameMode == TwoMachinesPlay) {
17128             SendToProgram("hard\n", &second);
17129         }
17130     } else {
17131         SendToProgram("easy\n", &first);
17132         thinkOutput[0] = NULLCHAR;
17133         if (gameMode == TwoMachinesPlay) {
17134             SendToProgram("easy\n", &second);
17135         }
17136     }
17137     appData.ponderNextMove = newState;
17138 }
17139
17140 void
17141 NewSettingEvent (int option, int *feature, char *command, int value)
17142 {
17143     char buf[MSG_SIZ];
17144
17145     if (gameMode == EditPosition) EditPositionDone(TRUE);
17146     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17147     if(feature == NULL || *feature) SendToProgram(buf, &first);
17148     if (gameMode == TwoMachinesPlay) {
17149         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17150     }
17151 }
17152
17153 void
17154 ShowThinkingEvent ()
17155 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17156 {
17157     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17158     int newState = appData.showThinking
17159         // [HGM] thinking: other features now need thinking output as well
17160         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17161
17162     if (oldState == newState) return;
17163     oldState = newState;
17164     if (gameMode == EditPosition) EditPositionDone(TRUE);
17165     if (oldState) {
17166         SendToProgram("post\n", &first);
17167         if (gameMode == TwoMachinesPlay) {
17168             SendToProgram("post\n", &second);
17169         }
17170     } else {
17171         SendToProgram("nopost\n", &first);
17172         thinkOutput[0] = NULLCHAR;
17173         if (gameMode == TwoMachinesPlay) {
17174             SendToProgram("nopost\n", &second);
17175         }
17176     }
17177 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17178 }
17179
17180 void
17181 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17182 {
17183   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17184   if (pr == NoProc) return;
17185   AskQuestion(title, question, replyPrefix, pr);
17186 }
17187
17188 void
17189 TypeInEvent (char firstChar)
17190 {
17191     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17192         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17193         gameMode == AnalyzeMode || gameMode == EditGame ||
17194         gameMode == EditPosition || gameMode == IcsExamining ||
17195         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17196         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17197                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17198                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17199         gameMode == Training) PopUpMoveDialog(firstChar);
17200 }
17201
17202 void
17203 TypeInDoneEvent (char *move)
17204 {
17205         Board board;
17206         int n, fromX, fromY, toX, toY;
17207         char promoChar;
17208         ChessMove moveType;
17209
17210         // [HGM] FENedit
17211         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17212                 EditPositionPasteFEN(move);
17213                 return;
17214         }
17215         // [HGM] movenum: allow move number to be typed in any mode
17216         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17217           ToNrEvent(2*n-1);
17218           return;
17219         }
17220         // undocumented kludge: allow command-line option to be typed in!
17221         // (potentially fatal, and does not implement the effect of the option.)
17222         // should only be used for options that are values on which future decisions will be made,
17223         // and definitely not on options that would be used during initialization.
17224         if(strstr(move, "!!! -") == move) {
17225             ParseArgsFromString(move+4);
17226             return;
17227         }
17228
17229       if (gameMode != EditGame && currentMove != forwardMostMove &&
17230         gameMode != Training) {
17231         DisplayMoveError(_("Displayed move is not current"));
17232       } else {
17233         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17234           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17235         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17236         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17237           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17238           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17239         } else {
17240           DisplayMoveError(_("Could not parse move"));
17241         }
17242       }
17243 }
17244
17245 void
17246 DisplayMove (int moveNumber)
17247 {
17248     char message[MSG_SIZ];
17249     char res[MSG_SIZ];
17250     char cpThinkOutput[MSG_SIZ];
17251
17252     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17253
17254     if (moveNumber == forwardMostMove - 1 ||
17255         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17256
17257         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17258
17259         if (strchr(cpThinkOutput, '\n')) {
17260             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17261         }
17262     } else {
17263         *cpThinkOutput = NULLCHAR;
17264     }
17265
17266     /* [AS] Hide thinking from human user */
17267     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17268         *cpThinkOutput = NULLCHAR;
17269         if( thinkOutput[0] != NULLCHAR ) {
17270             int i;
17271
17272             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17273                 cpThinkOutput[i] = '.';
17274             }
17275             cpThinkOutput[i] = NULLCHAR;
17276             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17277         }
17278     }
17279
17280     if (moveNumber == forwardMostMove - 1 &&
17281         gameInfo.resultDetails != NULL) {
17282         if (gameInfo.resultDetails[0] == NULLCHAR) {
17283           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17284         } else {
17285           snprintf(res, MSG_SIZ, " {%s} %s",
17286                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17287         }
17288     } else {
17289         res[0] = NULLCHAR;
17290     }
17291
17292     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17293         DisplayMessage(res, cpThinkOutput);
17294     } else {
17295       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17296                 WhiteOnMove(moveNumber) ? " " : ".. ",
17297                 parseList[moveNumber], res);
17298         DisplayMessage(message, cpThinkOutput);
17299     }
17300 }
17301
17302 void
17303 DisplayComment (int moveNumber, char *text)
17304 {
17305     char title[MSG_SIZ];
17306
17307     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17308       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17309     } else {
17310       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17311               WhiteOnMove(moveNumber) ? " " : ".. ",
17312               parseList[moveNumber]);
17313     }
17314     if (text != NULL && (appData.autoDisplayComment || commentUp))
17315         CommentPopUp(title, text);
17316 }
17317
17318 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17319  * might be busy thinking or pondering.  It can be omitted if your
17320  * gnuchess is configured to stop thinking immediately on any user
17321  * input.  However, that gnuchess feature depends on the FIONREAD
17322  * ioctl, which does not work properly on some flavors of Unix.
17323  */
17324 void
17325 Attention (ChessProgramState *cps)
17326 {
17327 #if ATTENTION
17328     if (!cps->useSigint) return;
17329     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17330     switch (gameMode) {
17331       case MachinePlaysWhite:
17332       case MachinePlaysBlack:
17333       case TwoMachinesPlay:
17334       case IcsPlayingWhite:
17335       case IcsPlayingBlack:
17336       case AnalyzeMode:
17337       case AnalyzeFile:
17338         /* Skip if we know it isn't thinking */
17339         if (!cps->maybeThinking) return;
17340         if (appData.debugMode)
17341           fprintf(debugFP, "Interrupting %s\n", cps->which);
17342         InterruptChildProcess(cps->pr);
17343         cps->maybeThinking = FALSE;
17344         break;
17345       default:
17346         break;
17347     }
17348 #endif /*ATTENTION*/
17349 }
17350
17351 int
17352 CheckFlags ()
17353 {
17354     if (whiteTimeRemaining <= 0) {
17355         if (!whiteFlag) {
17356             whiteFlag = TRUE;
17357             if (appData.icsActive) {
17358                 if (appData.autoCallFlag &&
17359                     gameMode == IcsPlayingBlack && !blackFlag) {
17360                   SendToICS(ics_prefix);
17361                   SendToICS("flag\n");
17362                 }
17363             } else {
17364                 if (blackFlag) {
17365                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17366                 } else {
17367                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17368                     if (appData.autoCallFlag) {
17369                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17370                         return TRUE;
17371                     }
17372                 }
17373             }
17374         }
17375     }
17376     if (blackTimeRemaining <= 0) {
17377         if (!blackFlag) {
17378             blackFlag = TRUE;
17379             if (appData.icsActive) {
17380                 if (appData.autoCallFlag &&
17381                     gameMode == IcsPlayingWhite && !whiteFlag) {
17382                   SendToICS(ics_prefix);
17383                   SendToICS("flag\n");
17384                 }
17385             } else {
17386                 if (whiteFlag) {
17387                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17388                 } else {
17389                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17390                     if (appData.autoCallFlag) {
17391                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17392                         return TRUE;
17393                     }
17394                 }
17395             }
17396         }
17397     }
17398     return FALSE;
17399 }
17400
17401 void
17402 CheckTimeControl ()
17403 {
17404     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17405         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17406
17407     /*
17408      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17409      */
17410     if ( !WhiteOnMove(forwardMostMove) ) {
17411         /* White made time control */
17412         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17413         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17414         /* [HGM] time odds: correct new time quota for time odds! */
17415                                             / WhitePlayer()->timeOdds;
17416         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17417     } else {
17418         lastBlack -= blackTimeRemaining;
17419         /* Black made time control */
17420         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17421                                             / WhitePlayer()->other->timeOdds;
17422         lastWhite = whiteTimeRemaining;
17423     }
17424 }
17425
17426 void
17427 DisplayBothClocks ()
17428 {
17429     int wom = gameMode == EditPosition ?
17430       !blackPlaysFirst : WhiteOnMove(currentMove);
17431     DisplayWhiteClock(whiteTimeRemaining, wom);
17432     DisplayBlackClock(blackTimeRemaining, !wom);
17433 }
17434
17435
17436 /* Timekeeping seems to be a portability nightmare.  I think everyone
17437    has ftime(), but I'm really not sure, so I'm including some ifdefs
17438    to use other calls if you don't.  Clocks will be less accurate if
17439    you have neither ftime nor gettimeofday.
17440 */
17441
17442 /* VS 2008 requires the #include outside of the function */
17443 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17444 #include <sys/timeb.h>
17445 #endif
17446
17447 /* Get the current time as a TimeMark */
17448 void
17449 GetTimeMark (TimeMark *tm)
17450 {
17451 #if HAVE_GETTIMEOFDAY
17452
17453     struct timeval timeVal;
17454     struct timezone timeZone;
17455
17456     gettimeofday(&timeVal, &timeZone);
17457     tm->sec = (long) timeVal.tv_sec;
17458     tm->ms = (int) (timeVal.tv_usec / 1000L);
17459
17460 #else /*!HAVE_GETTIMEOFDAY*/
17461 #if HAVE_FTIME
17462
17463 // include <sys/timeb.h> / moved to just above start of function
17464     struct timeb timeB;
17465
17466     ftime(&timeB);
17467     tm->sec = (long) timeB.time;
17468     tm->ms = (int) timeB.millitm;
17469
17470 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17471     tm->sec = (long) time(NULL);
17472     tm->ms = 0;
17473 #endif
17474 #endif
17475 }
17476
17477 /* Return the difference in milliseconds between two
17478    time marks.  We assume the difference will fit in a long!
17479 */
17480 long
17481 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17482 {
17483     return 1000L*(tm2->sec - tm1->sec) +
17484            (long) (tm2->ms - tm1->ms);
17485 }
17486
17487
17488 /*
17489  * Code to manage the game clocks.
17490  *
17491  * In tournament play, black starts the clock and then white makes a move.
17492  * We give the human user a slight advantage if he is playing white---the
17493  * clocks don't run until he makes his first move, so it takes zero time.
17494  * Also, we don't account for network lag, so we could get out of sync
17495  * with GNU Chess's clock -- but then, referees are always right.
17496  */
17497
17498 static TimeMark tickStartTM;
17499 static long intendedTickLength;
17500
17501 long
17502 NextTickLength (long timeRemaining)
17503 {
17504     long nominalTickLength, nextTickLength;
17505
17506     if (timeRemaining > 0L && timeRemaining <= 10000L)
17507       nominalTickLength = 100L;
17508     else
17509       nominalTickLength = 1000L;
17510     nextTickLength = timeRemaining % nominalTickLength;
17511     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17512
17513     return nextTickLength;
17514 }
17515
17516 /* Adjust clock one minute up or down */
17517 void
17518 AdjustClock (Boolean which, int dir)
17519 {
17520     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17521     if(which) blackTimeRemaining += 60000*dir;
17522     else      whiteTimeRemaining += 60000*dir;
17523     DisplayBothClocks();
17524     adjustedClock = TRUE;
17525 }
17526
17527 /* Stop clocks and reset to a fresh time control */
17528 void
17529 ResetClocks ()
17530 {
17531     (void) StopClockTimer();
17532     if (appData.icsActive) {
17533         whiteTimeRemaining = blackTimeRemaining = 0;
17534     } else if (searchTime) {
17535         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17536         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17537     } else { /* [HGM] correct new time quote for time odds */
17538         whiteTC = blackTC = fullTimeControlString;
17539         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17540         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17541     }
17542     if (whiteFlag || blackFlag) {
17543         DisplayTitle("");
17544         whiteFlag = blackFlag = FALSE;
17545     }
17546     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17547     DisplayBothClocks();
17548     adjustedClock = FALSE;
17549 }
17550
17551 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17552
17553 /* Decrement running clock by amount of time that has passed */
17554 void
17555 DecrementClocks ()
17556 {
17557     long timeRemaining;
17558     long lastTickLength, fudge;
17559     TimeMark now;
17560
17561     if (!appData.clockMode) return;
17562     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17563
17564     GetTimeMark(&now);
17565
17566     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17567
17568     /* Fudge if we woke up a little too soon */
17569     fudge = intendedTickLength - lastTickLength;
17570     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17571
17572     if (WhiteOnMove(forwardMostMove)) {
17573         if(whiteNPS >= 0) lastTickLength = 0;
17574         timeRemaining = whiteTimeRemaining -= lastTickLength;
17575         if(timeRemaining < 0 && !appData.icsActive) {
17576             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17577             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17578                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17579                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17580             }
17581         }
17582         DisplayWhiteClock(whiteTimeRemaining - fudge,
17583                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17584     } else {
17585         if(blackNPS >= 0) lastTickLength = 0;
17586         timeRemaining = blackTimeRemaining -= lastTickLength;
17587         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17588             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17589             if(suddenDeath) {
17590                 blackStartMove = forwardMostMove;
17591                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17592             }
17593         }
17594         DisplayBlackClock(blackTimeRemaining - fudge,
17595                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17596     }
17597     if (CheckFlags()) return;
17598
17599     if(twoBoards) { // count down secondary board's clocks as well
17600         activePartnerTime -= lastTickLength;
17601         partnerUp = 1;
17602         if(activePartner == 'W')
17603             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17604         else
17605             DisplayBlackClock(activePartnerTime, TRUE);
17606         partnerUp = 0;
17607     }
17608
17609     tickStartTM = now;
17610     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17611     StartClockTimer(intendedTickLength);
17612
17613     /* if the time remaining has fallen below the alarm threshold, sound the
17614      * alarm. if the alarm has sounded and (due to a takeback or time control
17615      * with increment) the time remaining has increased to a level above the
17616      * threshold, reset the alarm so it can sound again.
17617      */
17618
17619     if (appData.icsActive && appData.icsAlarm) {
17620
17621         /* make sure we are dealing with the user's clock */
17622         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17623                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17624            )) return;
17625
17626         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17627             alarmSounded = FALSE;
17628         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17629             PlayAlarmSound();
17630             alarmSounded = TRUE;
17631         }
17632     }
17633 }
17634
17635
17636 /* A player has just moved, so stop the previously running
17637    clock and (if in clock mode) start the other one.
17638    We redisplay both clocks in case we're in ICS mode, because
17639    ICS gives us an update to both clocks after every move.
17640    Note that this routine is called *after* forwardMostMove
17641    is updated, so the last fractional tick must be subtracted
17642    from the color that is *not* on move now.
17643 */
17644 void
17645 SwitchClocks (int newMoveNr)
17646 {
17647     long lastTickLength;
17648     TimeMark now;
17649     int flagged = FALSE;
17650
17651     GetTimeMark(&now);
17652
17653     if (StopClockTimer() && appData.clockMode) {
17654         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17655         if (!WhiteOnMove(forwardMostMove)) {
17656             if(blackNPS >= 0) lastTickLength = 0;
17657             blackTimeRemaining -= lastTickLength;
17658            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17659 //         if(pvInfoList[forwardMostMove].time == -1)
17660                  pvInfoList[forwardMostMove].time =               // use GUI time
17661                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17662         } else {
17663            if(whiteNPS >= 0) lastTickLength = 0;
17664            whiteTimeRemaining -= lastTickLength;
17665            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17666 //         if(pvInfoList[forwardMostMove].time == -1)
17667                  pvInfoList[forwardMostMove].time =
17668                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17669         }
17670         flagged = CheckFlags();
17671     }
17672     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17673     CheckTimeControl();
17674
17675     if (flagged || !appData.clockMode) return;
17676
17677     switch (gameMode) {
17678       case MachinePlaysBlack:
17679       case MachinePlaysWhite:
17680       case BeginningOfGame:
17681         if (pausing) return;
17682         break;
17683
17684       case EditGame:
17685       case PlayFromGameFile:
17686       case IcsExamining:
17687         return;
17688
17689       default:
17690         break;
17691     }
17692
17693     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17694         if(WhiteOnMove(forwardMostMove))
17695              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17696         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17697     }
17698
17699     tickStartTM = now;
17700     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17701       whiteTimeRemaining : blackTimeRemaining);
17702     StartClockTimer(intendedTickLength);
17703 }
17704
17705
17706 /* Stop both clocks */
17707 void
17708 StopClocks ()
17709 {
17710     long lastTickLength;
17711     TimeMark now;
17712
17713     if (!StopClockTimer()) return;
17714     if (!appData.clockMode) return;
17715
17716     GetTimeMark(&now);
17717
17718     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17719     if (WhiteOnMove(forwardMostMove)) {
17720         if(whiteNPS >= 0) lastTickLength = 0;
17721         whiteTimeRemaining -= lastTickLength;
17722         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17723     } else {
17724         if(blackNPS >= 0) lastTickLength = 0;
17725         blackTimeRemaining -= lastTickLength;
17726         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17727     }
17728     CheckFlags();
17729 }
17730
17731 /* Start clock of player on move.  Time may have been reset, so
17732    if clock is already running, stop and restart it. */
17733 void
17734 StartClocks ()
17735 {
17736     (void) StopClockTimer(); /* in case it was running already */
17737     DisplayBothClocks();
17738     if (CheckFlags()) return;
17739
17740     if (!appData.clockMode) return;
17741     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17742
17743     GetTimeMark(&tickStartTM);
17744     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17745       whiteTimeRemaining : blackTimeRemaining);
17746
17747    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17748     whiteNPS = blackNPS = -1;
17749     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17750        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17751         whiteNPS = first.nps;
17752     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17753        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17754         blackNPS = first.nps;
17755     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17756         whiteNPS = second.nps;
17757     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17758         blackNPS = second.nps;
17759     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17760
17761     StartClockTimer(intendedTickLength);
17762 }
17763
17764 char *
17765 TimeString (long ms)
17766 {
17767     long second, minute, hour, day;
17768     char *sign = "";
17769     static char buf[32];
17770
17771     if (ms > 0 && ms <= 9900) {
17772       /* convert milliseconds to tenths, rounding up */
17773       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17774
17775       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17776       return buf;
17777     }
17778
17779     /* convert milliseconds to seconds, rounding up */
17780     /* use floating point to avoid strangeness of integer division
17781        with negative dividends on many machines */
17782     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17783
17784     if (second < 0) {
17785         sign = "-";
17786         second = -second;
17787     }
17788
17789     day = second / (60 * 60 * 24);
17790     second = second % (60 * 60 * 24);
17791     hour = second / (60 * 60);
17792     second = second % (60 * 60);
17793     minute = second / 60;
17794     second = second % 60;
17795
17796     if (day > 0)
17797       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17798               sign, day, hour, minute, second);
17799     else if (hour > 0)
17800       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17801     else
17802       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17803
17804     return buf;
17805 }
17806
17807
17808 /*
17809  * This is necessary because some C libraries aren't ANSI C compliant yet.
17810  */
17811 char *
17812 StrStr (char *string, char *match)
17813 {
17814     int i, length;
17815
17816     length = strlen(match);
17817
17818     for (i = strlen(string) - length; i >= 0; i--, string++)
17819       if (!strncmp(match, string, length))
17820         return string;
17821
17822     return NULL;
17823 }
17824
17825 char *
17826 StrCaseStr (char *string, char *match)
17827 {
17828     int i, j, length;
17829
17830     length = strlen(match);
17831
17832     for (i = strlen(string) - length; i >= 0; i--, string++) {
17833         for (j = 0; j < length; j++) {
17834             if (ToLower(match[j]) != ToLower(string[j]))
17835               break;
17836         }
17837         if (j == length) return string;
17838     }
17839
17840     return NULL;
17841 }
17842
17843 #ifndef _amigados
17844 int
17845 StrCaseCmp (char *s1, char *s2)
17846 {
17847     char c1, c2;
17848
17849     for (;;) {
17850         c1 = ToLower(*s1++);
17851         c2 = ToLower(*s2++);
17852         if (c1 > c2) return 1;
17853         if (c1 < c2) return -1;
17854         if (c1 == NULLCHAR) return 0;
17855     }
17856 }
17857
17858
17859 int
17860 ToLower (int c)
17861 {
17862     return isupper(c) ? tolower(c) : c;
17863 }
17864
17865
17866 int
17867 ToUpper (int c)
17868 {
17869     return islower(c) ? toupper(c) : c;
17870 }
17871 #endif /* !_amigados    */
17872
17873 char *
17874 StrSave (char *s)
17875 {
17876   char *ret;
17877
17878   if ((ret = (char *) malloc(strlen(s) + 1)))
17879     {
17880       safeStrCpy(ret, s, strlen(s)+1);
17881     }
17882   return ret;
17883 }
17884
17885 char *
17886 StrSavePtr (char *s, char **savePtr)
17887 {
17888     if (*savePtr) {
17889         free(*savePtr);
17890     }
17891     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17892       safeStrCpy(*savePtr, s, strlen(s)+1);
17893     }
17894     return(*savePtr);
17895 }
17896
17897 char *
17898 PGNDate ()
17899 {
17900     time_t clock;
17901     struct tm *tm;
17902     char buf[MSG_SIZ];
17903
17904     clock = time((time_t *)NULL);
17905     tm = localtime(&clock);
17906     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17907             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17908     return StrSave(buf);
17909 }
17910
17911
17912 char *
17913 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17914 {
17915     int i, j, fromX, fromY, toX, toY;
17916     int whiteToPlay, haveRights = nrCastlingRights;
17917     char buf[MSG_SIZ];
17918     char *p, *q;
17919     int emptycount;
17920     ChessSquare piece;
17921
17922     whiteToPlay = (gameMode == EditPosition) ?
17923       !blackPlaysFirst : (move % 2 == 0);
17924     p = buf;
17925
17926     /* Piece placement data */
17927     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17928         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17929         emptycount = 0;
17930         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17931             if (boards[move][i][j] == EmptySquare) {
17932                 emptycount++;
17933             } else { ChessSquare piece = boards[move][i][j];
17934                 if (emptycount > 0) {
17935                     if(emptycount<10) /* [HGM] can be >= 10 */
17936                         *p++ = '0' + emptycount;
17937                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17938                     emptycount = 0;
17939                 }
17940                 if(PieceToChar(piece) == '+') {
17941                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17942                     *p++ = '+';
17943                     piece = (ChessSquare)(CHUDEMOTED piece);
17944                 }
17945                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17946                 if(*p = PieceSuffix(piece)) p++;
17947                 if(p[-1] == '~') {
17948                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17949                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17950                     *p++ = '~';
17951                 }
17952             }
17953         }
17954         if (emptycount > 0) {
17955             if(emptycount<10) /* [HGM] can be >= 10 */
17956                 *p++ = '0' + emptycount;
17957             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17958             emptycount = 0;
17959         }
17960         *p++ = '/';
17961     }
17962     *(p - 1) = ' ';
17963
17964     /* [HGM] print Crazyhouse or Shogi holdings */
17965     if( gameInfo.holdingsWidth ) {
17966         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17967         q = p;
17968         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17969             piece = boards[move][i][BOARD_WIDTH-1];
17970             if( piece != EmptySquare )
17971               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17972                   *p++ = PieceToChar(piece);
17973         }
17974         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17975             piece = boards[move][BOARD_HEIGHT-i-1][0];
17976             if( piece != EmptySquare )
17977               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17978                   *p++ = PieceToChar(piece);
17979         }
17980
17981         if( q == p ) *p++ = '-';
17982         *p++ = ']';
17983         *p++ = ' ';
17984     }
17985
17986     /* Active color */
17987     *p++ = whiteToPlay ? 'w' : 'b';
17988     *p++ = ' ';
17989
17990   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
17991     haveRights = 0; q = p;
17992     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
17993       piece = boards[move][0][i];
17994       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
17995         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
17996       }
17997     }
17998     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
17999       piece = boards[move][BOARD_HEIGHT-1][i];
18000       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18001         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18002       }
18003     }
18004     if(p == q) *p++ = '-';
18005     *p++ = ' ';
18006   }
18007
18008   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18009     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18010   } else {
18011   if(haveRights) {
18012      int handW=0, handB=0;
18013      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18014         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18015         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18016      }
18017      q = p;
18018      if(appData.fischerCastling) {
18019         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18020            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18021                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18022         } else {
18023        /* [HGM] write directly from rights */
18024            if(boards[move][CASTLING][2] != NoRights &&
18025               boards[move][CASTLING][0] != NoRights   )
18026                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18027            if(boards[move][CASTLING][2] != NoRights &&
18028               boards[move][CASTLING][1] != NoRights   )
18029                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18030         }
18031         if(handB) {
18032            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18033                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18034         } else {
18035            if(boards[move][CASTLING][5] != NoRights &&
18036               boards[move][CASTLING][3] != NoRights   )
18037                 *p++ = boards[move][CASTLING][3] + AAA;
18038            if(boards[move][CASTLING][5] != NoRights &&
18039               boards[move][CASTLING][4] != NoRights   )
18040                 *p++ = boards[move][CASTLING][4] + AAA;
18041         }
18042      } else {
18043
18044         /* [HGM] write true castling rights */
18045         if( nrCastlingRights == 6 ) {
18046             int q, k=0;
18047             if(boards[move][CASTLING][0] != NoRights &&
18048                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18049             q = (boards[move][CASTLING][1] != NoRights &&
18050                  boards[move][CASTLING][2] != NoRights  );
18051             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18052                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18053                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18054                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18055             }
18056             if(q) *p++ = 'Q';
18057             k = 0;
18058             if(boards[move][CASTLING][3] != NoRights &&
18059                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18060             q = (boards[move][CASTLING][4] != NoRights &&
18061                  boards[move][CASTLING][5] != NoRights  );
18062             if(handB) {
18063                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18064                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18065                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18066             }
18067             if(q) *p++ = 'q';
18068         }
18069      }
18070      if (q == p) *p++ = '-'; /* No castling rights */
18071      *p++ = ' ';
18072   }
18073
18074   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18075      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18076      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18077     /* En passant target square */
18078     if (move > backwardMostMove) {
18079         fromX = moveList[move - 1][0] - AAA;
18080         fromY = moveList[move - 1][1] - ONE;
18081         toX = moveList[move - 1][2] - AAA;
18082         toY = moveList[move - 1][3] - ONE;
18083         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18084             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18085             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18086             fromX == toX) {
18087             /* 2-square pawn move just happened */
18088             *p++ = toX + AAA;
18089             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18090         } else {
18091             *p++ = '-';
18092         }
18093     } else if(move == backwardMostMove) {
18094         // [HGM] perhaps we should always do it like this, and forget the above?
18095         if((signed char)boards[move][EP_STATUS] >= 0) {
18096             *p++ = boards[move][EP_STATUS] + AAA;
18097             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18098         } else {
18099             *p++ = '-';
18100         }
18101     } else {
18102         *p++ = '-';
18103     }
18104     *p++ = ' ';
18105   }
18106   }
18107
18108     if(moveCounts)
18109     {   int i = 0, j=move;
18110
18111         /* [HGM] find reversible plies */
18112         if (appData.debugMode) { int k;
18113             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18114             for(k=backwardMostMove; k<=forwardMostMove; k++)
18115                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18116
18117         }
18118
18119         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18120         if( j == backwardMostMove ) i += initialRulePlies;
18121         sprintf(p, "%d ", i);
18122         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18123
18124         /* Fullmove number */
18125         sprintf(p, "%d", (move / 2) + 1);
18126     } else *--p = NULLCHAR;
18127
18128     return StrSave(buf);
18129 }
18130
18131 Boolean
18132 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18133 {
18134     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18135     char *p, c;
18136     int emptycount, virgin[BOARD_FILES];
18137     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18138
18139     p = fen;
18140
18141     /* Piece placement data */
18142     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18143         j = 0;
18144         for (;;) {
18145             if (*p == '/' || *p == ' ' || *p == '[' ) {
18146                 if(j > w) w = j;
18147                 emptycount = gameInfo.boardWidth - j;
18148                 while (emptycount--)
18149                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18150                 if (*p == '/') p++;
18151                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18152                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18153                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18154                     }
18155                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18156                 }
18157                 break;
18158 #if(BOARD_FILES >= 10)*0
18159             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18160                 p++; emptycount=10;
18161                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18162                 while (emptycount--)
18163                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18164 #endif
18165             } else if (*p == '*') {
18166                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18167             } else if (isdigit(*p)) {
18168                 emptycount = *p++ - '0';
18169                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18170                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18171                 while (emptycount--)
18172                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18173             } else if (*p == '<') {
18174                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18175                 else if (i != 0 || !shuffle) return FALSE;
18176                 p++;
18177             } else if (shuffle && *p == '>') {
18178                 p++; // for now ignore closing shuffle range, and assume rank-end
18179             } else if (*p == '?') {
18180                 if (j >= gameInfo.boardWidth) return FALSE;
18181                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18182                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18183             } else if (*p == '+' || isalpha(*p)) {
18184                 char *q, *s = SUFFIXES;
18185                 if (j >= gameInfo.boardWidth) return FALSE;
18186                 if(*p=='+') {
18187                     char c = *++p;
18188                     if(q = strchr(s, p[1])) p++;
18189                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18190                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18191                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
18192                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18193                 } else {
18194                     char c = *p++;
18195                     if(q = strchr(s, *p)) p++;
18196                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18197                 }
18198
18199                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18200                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18201                     piece = (ChessSquare) (PROMOTED piece);
18202                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18203                     p++;
18204                 }
18205                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18206                 if(piece == king) wKingRank = i;
18207                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18208             } else {
18209                 return FALSE;
18210             }
18211         }
18212     }
18213     while (*p == '/' || *p == ' ') p++;
18214
18215     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18216
18217     /* [HGM] by default clear Crazyhouse holdings, if present */
18218     if(gameInfo.holdingsWidth) {
18219        for(i=0; i<BOARD_HEIGHT; i++) {
18220            board[i][0]             = EmptySquare; /* black holdings */
18221            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18222            board[i][1]             = (ChessSquare) 0; /* black counts */
18223            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18224        }
18225     }
18226
18227     /* [HGM] look for Crazyhouse holdings here */
18228     while(*p==' ') p++;
18229     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18230         int swap=0, wcnt=0, bcnt=0;
18231         if(*p == '[') p++;
18232         if(*p == '<') swap++, p++;
18233         if(*p == '-' ) p++; /* empty holdings */ else {
18234             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18235             /* if we would allow FEN reading to set board size, we would   */
18236             /* have to add holdings and shift the board read so far here   */
18237             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18238                 p++;
18239                 if((int) piece >= (int) BlackPawn ) {
18240                     i = (int)piece - (int)BlackPawn;
18241                     i = PieceToNumber((ChessSquare)i);
18242                     if( i >= gameInfo.holdingsSize ) return FALSE;
18243                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18244                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18245                     bcnt++;
18246                 } else {
18247                     i = (int)piece - (int)WhitePawn;
18248                     i = PieceToNumber((ChessSquare)i);
18249                     if( i >= gameInfo.holdingsSize ) return FALSE;
18250                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18251                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18252                     wcnt++;
18253                 }
18254             }
18255             if(subst) { // substitute back-rank question marks by holdings pieces
18256                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18257                     int k, m, n = bcnt + 1;
18258                     if(board[0][j] == ClearBoard) {
18259                         if(!wcnt) return FALSE;
18260                         n = rand() % wcnt;
18261                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18262                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18263                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18264                             break;
18265                         }
18266                     }
18267                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18268                         if(!bcnt) return FALSE;
18269                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18270                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18271                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18272                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18273                             break;
18274                         }
18275                     }
18276                 }
18277                 subst = 0;
18278             }
18279         }
18280         if(*p == ']') p++;
18281     }
18282
18283     if(subst) return FALSE; // substitution requested, but no holdings
18284
18285     while(*p == ' ') p++;
18286
18287     /* Active color */
18288     c = *p++;
18289     if(appData.colorNickNames) {
18290       if( c == appData.colorNickNames[0] ) c = 'w'; else
18291       if( c == appData.colorNickNames[1] ) c = 'b';
18292     }
18293     switch (c) {
18294       case 'w':
18295         *blackPlaysFirst = FALSE;
18296         break;
18297       case 'b':
18298         *blackPlaysFirst = TRUE;
18299         break;
18300       default:
18301         return FALSE;
18302     }
18303
18304     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18305     /* return the extra info in global variiables             */
18306
18307     while(*p==' ') p++;
18308
18309     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18310         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18311         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18312     }
18313
18314     /* set defaults in case FEN is incomplete */
18315     board[EP_STATUS] = EP_UNKNOWN;
18316     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18317     for(i=0; i<nrCastlingRights; i++ ) {
18318         board[CASTLING][i] =
18319             appData.fischerCastling ? NoRights : initialRights[i];
18320     }   /* assume possible unless obviously impossible */
18321     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18322     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18323     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18324                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18325     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18326     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18327     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18328                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18329     FENrulePlies = 0;
18330
18331     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18332       char *q = p;
18333       int w=0, b=0;
18334       while(isalpha(*p)) {
18335         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18336         if(islower(*p)) b |= 1 << (*p++ - 'a');
18337       }
18338       if(*p == '-') p++;
18339       if(p != q) {
18340         board[TOUCHED_W] = ~w;
18341         board[TOUCHED_B] = ~b;
18342         while(*p == ' ') p++;
18343       }
18344     } else
18345
18346     if(nrCastlingRights) {
18347       int fischer = 0;
18348       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18349       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18350           /* castling indicator present, so default becomes no castlings */
18351           for(i=0; i<nrCastlingRights; i++ ) {
18352                  board[CASTLING][i] = NoRights;
18353           }
18354       }
18355       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18356              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18357              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18358              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18359         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18360
18361         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18362             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18363             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18364         }
18365         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18366             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18367         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18368                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18369         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18370                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18371         switch(c) {
18372           case'K':
18373               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18374               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18375               board[CASTLING][2] = whiteKingFile;
18376               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18377               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18378               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18379               break;
18380           case'Q':
18381               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18382               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18383               board[CASTLING][2] = whiteKingFile;
18384               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18385               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18386               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18387               break;
18388           case'k':
18389               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18390               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18391               board[CASTLING][5] = blackKingFile;
18392               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18393               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18394               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18395               break;
18396           case'q':
18397               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18398               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18399               board[CASTLING][5] = blackKingFile;
18400               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18401               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18402               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18403           case '-':
18404               break;
18405           default: /* FRC castlings */
18406               if(c >= 'a') { /* black rights */
18407                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18408                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18409                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18410                   if(i == BOARD_RGHT) break;
18411                   board[CASTLING][5] = i;
18412                   c -= AAA;
18413                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18414                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18415                   if(c > i)
18416                       board[CASTLING][3] = c;
18417                   else
18418                       board[CASTLING][4] = c;
18419               } else { /* white rights */
18420                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18421                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18422                     if(board[0][i] == WhiteKing) break;
18423                   if(i == BOARD_RGHT) break;
18424                   board[CASTLING][2] = i;
18425                   c -= AAA - 'a' + 'A';
18426                   if(board[0][c] >= WhiteKing) break;
18427                   if(c > i)
18428                       board[CASTLING][0] = c;
18429                   else
18430                       board[CASTLING][1] = c;
18431               }
18432         }
18433       }
18434       for(i=0; i<nrCastlingRights; i++)
18435         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18436       if(gameInfo.variant == VariantSChess)
18437         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18438       if(fischer && shuffle) appData.fischerCastling = TRUE;
18439     if (appData.debugMode) {
18440         fprintf(debugFP, "FEN castling rights:");
18441         for(i=0; i<nrCastlingRights; i++)
18442         fprintf(debugFP, " %d", board[CASTLING][i]);
18443         fprintf(debugFP, "\n");
18444     }
18445
18446       while(*p==' ') p++;
18447     }
18448
18449     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18450
18451     /* read e.p. field in games that know e.p. capture */
18452     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18453        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18454        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18455       if(*p=='-') {
18456         p++; board[EP_STATUS] = EP_NONE;
18457       } else {
18458          char c = *p++ - AAA;
18459
18460          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18461          if(*p >= '0' && *p <='9') p++;
18462          board[EP_STATUS] = c;
18463       }
18464     }
18465
18466
18467     if(sscanf(p, "%d", &i) == 1) {
18468         FENrulePlies = i; /* 50-move ply counter */
18469         /* (The move number is still ignored)    */
18470     }
18471
18472     return TRUE;
18473 }
18474
18475 void
18476 EditPositionPasteFEN (char *fen)
18477 {
18478   if (fen != NULL) {
18479     Board initial_position;
18480
18481     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18482       DisplayError(_("Bad FEN position in clipboard"), 0);
18483       return ;
18484     } else {
18485       int savedBlackPlaysFirst = blackPlaysFirst;
18486       EditPositionEvent();
18487       blackPlaysFirst = savedBlackPlaysFirst;
18488       CopyBoard(boards[0], initial_position);
18489       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18490       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18491       DisplayBothClocks();
18492       DrawPosition(FALSE, boards[currentMove]);
18493     }
18494   }
18495 }
18496
18497 static char cseq[12] = "\\   ";
18498
18499 Boolean
18500 set_cont_sequence (char *new_seq)
18501 {
18502     int len;
18503     Boolean ret;
18504
18505     // handle bad attempts to set the sequence
18506         if (!new_seq)
18507                 return 0; // acceptable error - no debug
18508
18509     len = strlen(new_seq);
18510     ret = (len > 0) && (len < sizeof(cseq));
18511     if (ret)
18512       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18513     else if (appData.debugMode)
18514       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18515     return ret;
18516 }
18517
18518 /*
18519     reformat a source message so words don't cross the width boundary.  internal
18520     newlines are not removed.  returns the wrapped size (no null character unless
18521     included in source message).  If dest is NULL, only calculate the size required
18522     for the dest buffer.  lp argument indicats line position upon entry, and it's
18523     passed back upon exit.
18524 */
18525 int
18526 wrap (char *dest, char *src, int count, int width, int *lp)
18527 {
18528     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18529
18530     cseq_len = strlen(cseq);
18531     old_line = line = *lp;
18532     ansi = len = clen = 0;
18533
18534     for (i=0; i < count; i++)
18535     {
18536         if (src[i] == '\033')
18537             ansi = 1;
18538
18539         // if we hit the width, back up
18540         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18541         {
18542             // store i & len in case the word is too long
18543             old_i = i, old_len = len;
18544
18545             // find the end of the last word
18546             while (i && src[i] != ' ' && src[i] != '\n')
18547             {
18548                 i--;
18549                 len--;
18550             }
18551
18552             // word too long?  restore i & len before splitting it
18553             if ((old_i-i+clen) >= width)
18554             {
18555                 i = old_i;
18556                 len = old_len;
18557             }
18558
18559             // extra space?
18560             if (i && src[i-1] == ' ')
18561                 len--;
18562
18563             if (src[i] != ' ' && src[i] != '\n')
18564             {
18565                 i--;
18566                 if (len)
18567                     len--;
18568             }
18569
18570             // now append the newline and continuation sequence
18571             if (dest)
18572                 dest[len] = '\n';
18573             len++;
18574             if (dest)
18575                 strncpy(dest+len, cseq, cseq_len);
18576             len += cseq_len;
18577             line = cseq_len;
18578             clen = cseq_len;
18579             continue;
18580         }
18581
18582         if (dest)
18583             dest[len] = src[i];
18584         len++;
18585         if (!ansi)
18586             line++;
18587         if (src[i] == '\n')
18588             line = 0;
18589         if (src[i] == 'm')
18590             ansi = 0;
18591     }
18592     if (dest && appData.debugMode)
18593     {
18594         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18595             count, width, line, len, *lp);
18596         show_bytes(debugFP, src, count);
18597         fprintf(debugFP, "\ndest: ");
18598         show_bytes(debugFP, dest, len);
18599         fprintf(debugFP, "\n");
18600     }
18601     *lp = dest ? line : old_line;
18602
18603     return len;
18604 }
18605
18606 // [HGM] vari: routines for shelving variations
18607 Boolean modeRestore = FALSE;
18608
18609 void
18610 PushInner (int firstMove, int lastMove)
18611 {
18612         int i, j, nrMoves = lastMove - firstMove;
18613
18614         // push current tail of game on stack
18615         savedResult[storedGames] = gameInfo.result;
18616         savedDetails[storedGames] = gameInfo.resultDetails;
18617         gameInfo.resultDetails = NULL;
18618         savedFirst[storedGames] = firstMove;
18619         savedLast [storedGames] = lastMove;
18620         savedFramePtr[storedGames] = framePtr;
18621         framePtr -= nrMoves; // reserve space for the boards
18622         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18623             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18624             for(j=0; j<MOVE_LEN; j++)
18625                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18626             for(j=0; j<2*MOVE_LEN; j++)
18627                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18628             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18629             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18630             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18631             pvInfoList[firstMove+i-1].depth = 0;
18632             commentList[framePtr+i] = commentList[firstMove+i];
18633             commentList[firstMove+i] = NULL;
18634         }
18635
18636         storedGames++;
18637         forwardMostMove = firstMove; // truncate game so we can start variation
18638 }
18639
18640 void
18641 PushTail (int firstMove, int lastMove)
18642 {
18643         if(appData.icsActive) { // only in local mode
18644                 forwardMostMove = currentMove; // mimic old ICS behavior
18645                 return;
18646         }
18647         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18648
18649         PushInner(firstMove, lastMove);
18650         if(storedGames == 1) GreyRevert(FALSE);
18651         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18652 }
18653
18654 void
18655 PopInner (Boolean annotate)
18656 {
18657         int i, j, nrMoves;
18658         char buf[8000], moveBuf[20];
18659
18660         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18661         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18662         nrMoves = savedLast[storedGames] - currentMove;
18663         if(annotate) {
18664                 int cnt = 10;
18665                 if(!WhiteOnMove(currentMove))
18666                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18667                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18668                 for(i=currentMove; i<forwardMostMove; i++) {
18669                         if(WhiteOnMove(i))
18670                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18671                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18672                         strcat(buf, moveBuf);
18673                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18674                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18675                 }
18676                 strcat(buf, ")");
18677         }
18678         for(i=1; i<=nrMoves; i++) { // copy last variation back
18679             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18680             for(j=0; j<MOVE_LEN; j++)
18681                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18682             for(j=0; j<2*MOVE_LEN; j++)
18683                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18684             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18685             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18686             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18687             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18688             commentList[currentMove+i] = commentList[framePtr+i];
18689             commentList[framePtr+i] = NULL;
18690         }
18691         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18692         framePtr = savedFramePtr[storedGames];
18693         gameInfo.result = savedResult[storedGames];
18694         if(gameInfo.resultDetails != NULL) {
18695             free(gameInfo.resultDetails);
18696       }
18697         gameInfo.resultDetails = savedDetails[storedGames];
18698         forwardMostMove = currentMove + nrMoves;
18699 }
18700
18701 Boolean
18702 PopTail (Boolean annotate)
18703 {
18704         if(appData.icsActive) return FALSE; // only in local mode
18705         if(!storedGames) return FALSE; // sanity
18706         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18707
18708         PopInner(annotate);
18709         if(currentMove < forwardMostMove) ForwardEvent(); else
18710         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18711
18712         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18713         return TRUE;
18714 }
18715
18716 void
18717 CleanupTail ()
18718 {       // remove all shelved variations
18719         int i;
18720         for(i=0; i<storedGames; i++) {
18721             if(savedDetails[i])
18722                 free(savedDetails[i]);
18723             savedDetails[i] = NULL;
18724         }
18725         for(i=framePtr; i<MAX_MOVES; i++) {
18726                 if(commentList[i]) free(commentList[i]);
18727                 commentList[i] = NULL;
18728         }
18729         framePtr = MAX_MOVES-1;
18730         storedGames = 0;
18731 }
18732
18733 void
18734 LoadVariation (int index, char *text)
18735 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18736         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18737         int level = 0, move;
18738
18739         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18740         // first find outermost bracketing variation
18741         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18742             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18743                 if(*p == '{') wait = '}'; else
18744                 if(*p == '[') wait = ']'; else
18745                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18746                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18747             }
18748             if(*p == wait) wait = NULLCHAR; // closing ]} found
18749             p++;
18750         }
18751         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18752         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18753         end[1] = NULLCHAR; // clip off comment beyond variation
18754         ToNrEvent(currentMove-1);
18755         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18756         // kludge: use ParsePV() to append variation to game
18757         move = currentMove;
18758         ParsePV(start, TRUE, TRUE);
18759         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18760         ClearPremoveHighlights();
18761         CommentPopDown();
18762         ToNrEvent(currentMove+1);
18763 }
18764
18765 void
18766 LoadTheme ()
18767 {
18768     char *p, *q, buf[MSG_SIZ];
18769     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18770         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18771         ParseArgsFromString(buf);
18772         ActivateTheme(TRUE); // also redo colors
18773         return;
18774     }
18775     p = nickName;
18776     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18777     {
18778         int len;
18779         q = appData.themeNames;
18780         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18781       if(appData.useBitmaps) {
18782         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18783                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18784                 appData.liteBackTextureMode,
18785                 appData.darkBackTextureMode );
18786       } else {
18787         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18788                 Col2Text(2),   // lightSquareColor
18789                 Col2Text(3) ); // darkSquareColor
18790       }
18791       if(appData.useBorder) {
18792         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18793                 appData.border);
18794       } else {
18795         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18796       }
18797       if(appData.useFont) {
18798         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18799                 appData.renderPiecesWithFont,
18800                 appData.fontToPieceTable,
18801                 Col2Text(9),    // appData.fontBackColorWhite
18802                 Col2Text(10) ); // appData.fontForeColorBlack
18803       } else {
18804         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18805                 appData.pieceDirectory);
18806         if(!appData.pieceDirectory[0])
18807           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18808                 Col2Text(0),   // whitePieceColor
18809                 Col2Text(1) ); // blackPieceColor
18810       }
18811       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18812                 Col2Text(4),   // highlightSquareColor
18813                 Col2Text(5) ); // premoveHighlightColor
18814         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18815         if(insert != q) insert[-1] = NULLCHAR;
18816         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18817         if(q)   free(q);
18818     }
18819     ActivateTheme(FALSE);
18820 }