Print castlings as double move
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58     int flock(int f, int code);
59 #   define LOCK_EX 2
60 #   define SLASH '\\'
61
62 #   ifdef ARC_64BIT
63 #       define EGBB_NAME "egbbdll64.dll"
64 #   else
65 #       define EGBB_NAME "egbbdll.dll"
66 #   endif
67
68 #else
69
70 #   include <sys/file.h>
71 #   define SLASH '/'
72
73 #   include <dlfcn.h>
74 #   ifdef ARC_64BIT
75 #       define EGBB_NAME "egbbso64.so"
76 #   else
77 #       define EGBB_NAME "egbbso.so"
78 #   endif
79     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
80 #   define CDECL
81 #   define HMODULE void *
82 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 #   define GetProcAddress dlsym
84
85 #endif
86
87 #include "config.h"
88
89 #include <assert.h>
90 #include <stdio.h>
91 #include <ctype.h>
92 #include <errno.h>
93 #include <sys/types.h>
94 #include <sys/stat.h>
95 #include <math.h>
96 #include <ctype.h>
97
98 #if STDC_HEADERS
99 # include <stdlib.h>
100 # include <string.h>
101 # include <stdarg.h>
102 #else /* not STDC_HEADERS */
103 # if HAVE_STRING_H
104 #  include <string.h>
105 # else /* not HAVE_STRING_H */
106 #  include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
109
110 #if HAVE_SYS_FCNTL_H
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
113 # if HAVE_FCNTL_H
114 #  include <fcntl.h>
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
117
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
120 # include <time.h>
121 #else
122 # if HAVE_SYS_TIME_H
123 #  include <sys/time.h>
124 # else
125 #  include <time.h>
126 # endif
127 #endif
128
129 #if defined(_amigados) && !defined(__GNUC__)
130 struct timezone {
131     int tz_minuteswest;
132     int tz_dsttime;
133 };
134 extern int gettimeofday(struct timeval *, struct timezone *);
135 #endif
136
137 #if HAVE_UNISTD_H
138 # include <unistd.h>
139 #endif
140
141 #include "common.h"
142 #include "frontend.h"
143 #include "backend.h"
144 #include "parser.h"
145 #include "moves.h"
146 #if ZIPPY
147 # include "zippy.h"
148 #endif
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
152 #include "gettext.h"
153
154 #ifdef ENABLE_NLS
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
158 #else
159 # ifdef WIN32
160 #   define _(s) T_(s)
161 #   define N_(s) s
162 # else
163 #   define _(s) (s)
164 #   define N_(s) s
165 #   define T_(s) s
166 # endif
167 #endif
168
169
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172                          char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174                       char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187                    /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void StartChessProgram P((ChessProgramState *cps));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
268 int endPV = -1;
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
272 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
276 Boolean partnerUp;
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
288 int chattingPartner;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298 int border;       /* [HGM] width of board rim, needed to size seek graph  */
299
300 /* States for ics_getting_history */
301 #define H_FALSE 0
302 #define H_REQUESTED 1
303 #define H_GOT_REQ_HEADER 2
304 #define H_GOT_UNREQ_HEADER 3
305 #define H_GETTING_MOVES 4
306 #define H_GOT_UNWANTED_HEADER 5
307
308 /* whosays values for GameEnds */
309 #define GE_ICS 0
310 #define GE_ENGINE 1
311 #define GE_PLAYER 2
312 #define GE_FILE 3
313 #define GE_XBOARD 4
314 #define GE_ENGINE1 5
315 #define GE_ENGINE2 6
316
317 /* Maximum number of games in a cmail message */
318 #define CMAIL_MAX_GAMES 20
319
320 /* Different types of move when calling RegisterMove */
321 #define CMAIL_MOVE   0
322 #define CMAIL_RESIGN 1
323 #define CMAIL_DRAW   2
324 #define CMAIL_ACCEPT 3
325
326 /* Different types of result to remember for each game */
327 #define CMAIL_NOT_RESULT 0
328 #define CMAIL_OLD_RESULT 1
329 #define CMAIL_NEW_RESULT 2
330
331 /* Telnet protocol constants */
332 #define TN_WILL 0373
333 #define TN_WONT 0374
334 #define TN_DO   0375
335 #define TN_DONT 0376
336 #define TN_IAC  0377
337 #define TN_ECHO 0001
338 #define TN_SGA  0003
339 #define TN_PORT 23
340
341 char*
342 safeStrCpy (char *dst, const char *src, size_t count)
343 { // [HGM] made safe
344   int i;
345   assert( dst != NULL );
346   assert( src != NULL );
347   assert( count > 0 );
348
349   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
350   if(  i == count && dst[count-1] != NULLCHAR)
351     {
352       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
353       if(appData.debugMode)
354         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
355     }
356
357   return dst;
358 }
359
360 /* Some compiler can't cast u64 to double
361  * This function do the job for us:
362
363  * We use the highest bit for cast, this only
364  * works if the highest bit is not
365  * in use (This should not happen)
366  *
367  * We used this for all compiler
368  */
369 double
370 u64ToDouble (u64 value)
371 {
372   double r;
373   u64 tmp = value & u64Const(0x7fffffffffffffff);
374   r = (double)(s64)tmp;
375   if (value & u64Const(0x8000000000000000))
376        r +=  9.2233720368547758080e18; /* 2^63 */
377  return r;
378 }
379
380 /* Fake up flags for now, as we aren't keeping track of castling
381    availability yet. [HGM] Change of logic: the flag now only
382    indicates the type of castlings allowed by the rule of the game.
383    The actual rights themselves are maintained in the array
384    castlingRights, as part of the game history, and are not probed
385    by this function.
386  */
387 int
388 PosFlags (index)
389 {
390   int flags = F_ALL_CASTLE_OK;
391   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
392   switch (gameInfo.variant) {
393   case VariantSuicide:
394     flags &= ~F_ALL_CASTLE_OK;
395   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
396     flags |= F_IGNORE_CHECK;
397   case VariantLosers:
398     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
399     break;
400   case VariantAtomic:
401     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
402     break;
403   case VariantKriegspiel:
404     flags |= F_KRIEGSPIEL_CAPTURE;
405     break;
406   case VariantCapaRandom:
407   case VariantFischeRandom:
408     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
409   case VariantNoCastle:
410   case VariantShatranj:
411   case VariantCourier:
412   case VariantMakruk:
413   case VariantASEAN:
414   case VariantGrand:
415     flags &= ~F_ALL_CASTLE_OK;
416     break;
417   case VariantChu:
418   case VariantChuChess:
419   case VariantLion:
420     flags |= F_NULL_MOVE;
421     break;
422   default:
423     break;
424   }
425   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
426   return flags;
427 }
428
429 FILE *gameFileFP, *debugFP, *serverFP;
430 char *currentDebugFile; // [HGM] debug split: to remember name
431
432 /*
433     [AS] Note: sometimes, the sscanf() function is used to parse the input
434     into a fixed-size buffer. Because of this, we must be prepared to
435     receive strings as long as the size of the input buffer, which is currently
436     set to 4K for Windows and 8K for the rest.
437     So, we must either allocate sufficiently large buffers here, or
438     reduce the size of the input buffer in the input reading part.
439 */
440
441 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
442 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
443 char thinkOutput1[MSG_SIZ*10];
444
445 ChessProgramState first, second, pairing;
446
447 /* premove variables */
448 int premoveToX = 0;
449 int premoveToY = 0;
450 int premoveFromX = 0;
451 int premoveFromY = 0;
452 int premovePromoChar = 0;
453 int gotPremove = 0;
454 Boolean alarmSounded;
455 /* end premove variables */
456
457 char *ics_prefix = "$";
458 enum ICS_TYPE ics_type = ICS_GENERIC;
459
460 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
461 int pauseExamForwardMostMove = 0;
462 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
463 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
464 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
465 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
466 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
467 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
468 int whiteFlag = FALSE, blackFlag = FALSE;
469 int userOfferedDraw = FALSE;
470 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
471 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
472 int cmailMoveType[CMAIL_MAX_GAMES];
473 long ics_clock_paused = 0;
474 ProcRef icsPR = NoProc, cmailPR = NoProc;
475 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
476 GameMode gameMode = BeginningOfGame;
477 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
478 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
479 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
480 int hiddenThinkOutputState = 0; /* [AS] */
481 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
482 int adjudicateLossPlies = 6;
483 char white_holding[64], black_holding[64];
484 TimeMark lastNodeCountTime;
485 long lastNodeCount=0;
486 int shiftKey, controlKey; // [HGM] set by mouse handler
487
488 int have_sent_ICS_logon = 0;
489 int movesPerSession;
490 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
491 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
492 Boolean adjustedClock;
493 long timeControl_2; /* [AS] Allow separate time controls */
494 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
495 long timeRemaining[2][MAX_MOVES];
496 int matchGame = 0, nextGame = 0, roundNr = 0;
497 Boolean waitingForGame = FALSE, startingEngine = FALSE;
498 TimeMark programStartTime, pauseStart;
499 char ics_handle[MSG_SIZ];
500 int have_set_title = 0;
501
502 /* animateTraining preserves the state of appData.animate
503  * when Training mode is activated. This allows the
504  * response to be animated when appData.animate == TRUE and
505  * appData.animateDragging == TRUE.
506  */
507 Boolean animateTraining;
508
509 GameInfo gameInfo;
510
511 AppData appData;
512
513 Board boards[MAX_MOVES];
514 /* [HGM] Following 7 needed for accurate legality tests: */
515 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
516 signed char  initialRights[BOARD_FILES];
517 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
518 int   initialRulePlies, FENrulePlies;
519 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
520 int loadFlag = 0;
521 Boolean shuffleOpenings;
522 int mute; // mute all sounds
523
524 // [HGM] vari: next 12 to save and restore variations
525 #define MAX_VARIATIONS 10
526 int framePtr = MAX_MOVES-1; // points to free stack entry
527 int storedGames = 0;
528 int savedFirst[MAX_VARIATIONS];
529 int savedLast[MAX_VARIATIONS];
530 int savedFramePtr[MAX_VARIATIONS];
531 char *savedDetails[MAX_VARIATIONS];
532 ChessMove savedResult[MAX_VARIATIONS];
533
534 void PushTail P((int firstMove, int lastMove));
535 Boolean PopTail P((Boolean annotate));
536 void PushInner P((int firstMove, int lastMove));
537 void PopInner P((Boolean annotate));
538 void CleanupTail P((void));
539
540 ChessSquare  FIDEArray[2][BOARD_FILES] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
542         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
544         BlackKing, BlackBishop, BlackKnight, BlackRook }
545 };
546
547 ChessSquare twoKingsArray[2][BOARD_FILES] = {
548     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
549         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
551         BlackKing, BlackKing, BlackKnight, BlackRook }
552 };
553
554 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
555     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
556         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
557     { BlackRook, BlackMan, BlackBishop, BlackQueen,
558         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
559 };
560
561 ChessSquare SpartanArray[2][BOARD_FILES] = {
562     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
563         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
564     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
565         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
566 };
567
568 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
569     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
570         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
571     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
572         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
573 };
574
575 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
576     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
577         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
578     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
579         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
580 };
581
582 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
583     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
584         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
585     { BlackRook, BlackKnight, BlackMan, BlackFerz,
586         BlackKing, BlackMan, BlackKnight, BlackRook }
587 };
588
589 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
590     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
591         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
592     { BlackRook, BlackKnight, BlackMan, BlackFerz,
593         BlackKing, BlackMan, BlackKnight, BlackRook }
594 };
595
596 ChessSquare  lionArray[2][BOARD_FILES] = {
597     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
598         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
599     { BlackRook, BlackLion, BlackBishop, BlackQueen,
600         BlackKing, BlackBishop, BlackKnight, BlackRook }
601 };
602
603
604 #if (BOARD_FILES>=10)
605 ChessSquare ShogiArray[2][BOARD_FILES] = {
606     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
607         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
608     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
609         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
610 };
611
612 ChessSquare XiangqiArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
614         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
616         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
617 };
618
619 ChessSquare CapablancaArray[2][BOARD_FILES] = {
620     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
621         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
622     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
623         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
624 };
625
626 ChessSquare GreatArray[2][BOARD_FILES] = {
627     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
628         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
629     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
630         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
631 };
632
633 ChessSquare JanusArray[2][BOARD_FILES] = {
634     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
635         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
636     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
637         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
638 };
639
640 ChessSquare GrandArray[2][BOARD_FILES] = {
641     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
642         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
643     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
644         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
645 };
646
647 ChessSquare ChuChessArray[2][BOARD_FILES] = {
648     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
649         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
650     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
651         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
652 };
653
654 #ifdef GOTHIC
655 ChessSquare GothicArray[2][BOARD_FILES] = {
656     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
657         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
658     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
659         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
660 };
661 #else // !GOTHIC
662 #define GothicArray CapablancaArray
663 #endif // !GOTHIC
664
665 #ifdef FALCON
666 ChessSquare FalconArray[2][BOARD_FILES] = {
667     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
668         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
669     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
670         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
671 };
672 #else // !FALCON
673 #define FalconArray CapablancaArray
674 #endif // !FALCON
675
676 #else // !(BOARD_FILES>=10)
677 #define XiangqiPosition FIDEArray
678 #define CapablancaArray FIDEArray
679 #define GothicArray FIDEArray
680 #define GreatArray FIDEArray
681 #endif // !(BOARD_FILES>=10)
682
683 #if (BOARD_FILES>=12)
684 ChessSquare CourierArray[2][BOARD_FILES] = {
685     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
686         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
687     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
688         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
689 };
690 ChessSquare ChuArray[6][BOARD_FILES] = {
691     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
692       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
693     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
694       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
695     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
696       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
697     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
698       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
699     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
700       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
701     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
702       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
703 };
704 #else // !(BOARD_FILES>=12)
705 #define CourierArray CapablancaArray
706 #define ChuArray CapablancaArray
707 #endif // !(BOARD_FILES>=12)
708
709
710 Board initialPosition;
711
712
713 /* Convert str to a rating. Checks for special cases of "----",
714
715    "++++", etc. Also strips ()'s */
716 int
717 string_to_rating (char *str)
718 {
719   while(*str && !isdigit(*str)) ++str;
720   if (!*str)
721     return 0;   /* One of the special "no rating" cases */
722   else
723     return atoi(str);
724 }
725
726 void
727 ClearProgramStats ()
728 {
729     /* Init programStats */
730     programStats.movelist[0] = 0;
731     programStats.depth = 0;
732     programStats.nr_moves = 0;
733     programStats.moves_left = 0;
734     programStats.nodes = 0;
735     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
736     programStats.score = 0;
737     programStats.got_only_move = 0;
738     programStats.got_fail = 0;
739     programStats.line_is_book = 0;
740 }
741
742 void
743 CommonEngineInit ()
744 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
745     if (appData.firstPlaysBlack) {
746         first.twoMachinesColor = "black\n";
747         second.twoMachinesColor = "white\n";
748     } else {
749         first.twoMachinesColor = "white\n";
750         second.twoMachinesColor = "black\n";
751     }
752
753     first.other = &second;
754     second.other = &first;
755
756     { float norm = 1;
757         if(appData.timeOddsMode) {
758             norm = appData.timeOdds[0];
759             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
760         }
761         first.timeOdds  = appData.timeOdds[0]/norm;
762         second.timeOdds = appData.timeOdds[1]/norm;
763     }
764
765     if(programVersion) free(programVersion);
766     if (appData.noChessProgram) {
767         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
768         sprintf(programVersion, "%s", PACKAGE_STRING);
769     } else {
770       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
771       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
772       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
773     }
774 }
775
776 void
777 UnloadEngine (ChessProgramState *cps)
778 {
779         /* Kill off first chess program */
780         if (cps->isr != NULL)
781           RemoveInputSource(cps->isr);
782         cps->isr = NULL;
783
784         if (cps->pr != NoProc) {
785             ExitAnalyzeMode();
786             DoSleep( appData.delayBeforeQuit );
787             SendToProgram("quit\n", cps);
788             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
789         }
790         cps->pr = NoProc;
791         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
792 }
793
794 void
795 ClearOptions (ChessProgramState *cps)
796 {
797     int i;
798     cps->nrOptions = cps->comboCnt = 0;
799     for(i=0; i<MAX_OPTIONS; i++) {
800         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
801         cps->option[i].textValue = 0;
802     }
803 }
804
805 char *engineNames[] = {
806   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
807      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
808 N_("first"),
809   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
810      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
811 N_("second")
812 };
813
814 void
815 InitEngine (ChessProgramState *cps, int n)
816 {   // [HGM] all engine initialiation put in a function that does one engine
817
818     ClearOptions(cps);
819
820     cps->which = engineNames[n];
821     cps->maybeThinking = FALSE;
822     cps->pr = NoProc;
823     cps->isr = NULL;
824     cps->sendTime = 2;
825     cps->sendDrawOffers = 1;
826
827     cps->program = appData.chessProgram[n];
828     cps->host = appData.host[n];
829     cps->dir = appData.directory[n];
830     cps->initString = appData.engInitString[n];
831     cps->computerString = appData.computerString[n];
832     cps->useSigint  = TRUE;
833     cps->useSigterm = TRUE;
834     cps->reuse = appData.reuse[n];
835     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
836     cps->useSetboard = FALSE;
837     cps->useSAN = FALSE;
838     cps->usePing = FALSE;
839     cps->lastPing = 0;
840     cps->lastPong = 0;
841     cps->usePlayother = FALSE;
842     cps->useColors = TRUE;
843     cps->useUsermove = FALSE;
844     cps->sendICS = FALSE;
845     cps->sendName = appData.icsActive;
846     cps->sdKludge = FALSE;
847     cps->stKludge = FALSE;
848     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
849     TidyProgramName(cps->program, cps->host, cps->tidy);
850     cps->matchWins = 0;
851     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
852     cps->analysisSupport = 2; /* detect */
853     cps->analyzing = FALSE;
854     cps->initDone = FALSE;
855     cps->reload = FALSE;
856     cps->pseudo = appData.pseudo[n];
857
858     /* New features added by Tord: */
859     cps->useFEN960 = FALSE;
860     cps->useOOCastle = TRUE;
861     /* End of new features added by Tord. */
862     cps->fenOverride  = appData.fenOverride[n];
863
864     /* [HGM] time odds: set factor for each machine */
865     cps->timeOdds  = appData.timeOdds[n];
866
867     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
868     cps->accumulateTC = appData.accumulateTC[n];
869     cps->maxNrOfSessions = 1;
870
871     /* [HGM] debug */
872     cps->debug = FALSE;
873
874     cps->drawDepth = appData.drawDepth[n];
875     cps->supportsNPS = UNKNOWN;
876     cps->memSize = FALSE;
877     cps->maxCores = FALSE;
878     ASSIGN(cps->egtFormats, "");
879
880     /* [HGM] options */
881     cps->optionSettings  = appData.engOptions[n];
882
883     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
884     cps->isUCI = appData.isUCI[n]; /* [AS] */
885     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
886     cps->highlight = 0;
887
888     if (appData.protocolVersion[n] > PROTOVER
889         || appData.protocolVersion[n] < 1)
890       {
891         char buf[MSG_SIZ];
892         int len;
893
894         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
895                        appData.protocolVersion[n]);
896         if( (len >= MSG_SIZ) && appData.debugMode )
897           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
898
899         DisplayFatalError(buf, 0, 2);
900       }
901     else
902       {
903         cps->protocolVersion = appData.protocolVersion[n];
904       }
905
906     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
907     ParseFeatures(appData.featureDefaults, cps);
908 }
909
910 ChessProgramState *savCps;
911
912 GameMode oldMode;
913
914 void
915 LoadEngine ()
916 {
917     int i;
918     if(WaitForEngine(savCps, LoadEngine)) return;
919     CommonEngineInit(); // recalculate time odds
920     if(gameInfo.variant != StringToVariant(appData.variant)) {
921         // we changed variant when loading the engine; this forces us to reset
922         Reset(TRUE, savCps != &first);
923         oldMode = BeginningOfGame; // to prevent restoring old mode
924     }
925     InitChessProgram(savCps, FALSE);
926     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
927     DisplayMessage("", "");
928     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
929     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
930     ThawUI();
931     SetGNUMode();
932     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
933 }
934
935 void
936 ReplaceEngine (ChessProgramState *cps, int n)
937 {
938     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
939     keepInfo = 1;
940     if(oldMode != BeginningOfGame) EditGameEvent();
941     keepInfo = 0;
942     UnloadEngine(cps);
943     appData.noChessProgram = FALSE;
944     appData.clockMode = TRUE;
945     InitEngine(cps, n);
946     UpdateLogos(TRUE);
947     if(n) return; // only startup first engine immediately; second can wait
948     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
949     LoadEngine();
950 }
951
952 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
953 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
954
955 static char resetOptions[] =
956         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
957         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
958         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
959         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
960
961 void
962 FloatToFront(char **list, char *engineLine)
963 {
964     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
965     int i=0;
966     if(appData.recentEngines <= 0) return;
967     TidyProgramName(engineLine, "localhost", tidy+1);
968     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
969     strncpy(buf+1, *list, MSG_SIZ-50);
970     if(p = strstr(buf, tidy)) { // tidy name appears in list
971         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
972         while(*p++ = *++q); // squeeze out
973     }
974     strcat(tidy, buf+1); // put list behind tidy name
975     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
976     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
977     ASSIGN(*list, tidy+1);
978 }
979
980 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
981
982 void
983 Load (ChessProgramState *cps, int i)
984 {
985     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
986     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
987         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
988         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
989         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
990         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
991         appData.firstProtocolVersion = PROTOVER;
992         ParseArgsFromString(buf);
993         SwapEngines(i);
994         ReplaceEngine(cps, i);
995         FloatToFront(&appData.recentEngineList, engineLine);
996         return;
997     }
998     p = engineName;
999     while(q = strchr(p, SLASH)) p = q+1;
1000     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1001     if(engineDir[0] != NULLCHAR) {
1002         ASSIGN(appData.directory[i], engineDir); p = engineName;
1003     } else if(p != engineName) { // derive directory from engine path, when not given
1004         p[-1] = 0;
1005         ASSIGN(appData.directory[i], engineName);
1006         p[-1] = SLASH;
1007         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1008     } else { ASSIGN(appData.directory[i], "."); }
1009     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1010     if(params[0]) {
1011         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1012         snprintf(command, MSG_SIZ, "%s %s", p, params);
1013         p = command;
1014     }
1015     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1016     ASSIGN(appData.chessProgram[i], p);
1017     appData.isUCI[i] = isUCI;
1018     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1019     appData.hasOwnBookUCI[i] = hasBook;
1020     if(!nickName[0]) useNick = FALSE;
1021     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1022     if(addToList) {
1023         int len;
1024         char quote;
1025         q = firstChessProgramNames;
1026         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1027         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1028         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1029                         quote, p, quote, appData.directory[i],
1030                         useNick ? " -fn \"" : "",
1031                         useNick ? nickName : "",
1032                         useNick ? "\"" : "",
1033                         v1 ? " -firstProtocolVersion 1" : "",
1034                         hasBook ? "" : " -fNoOwnBookUCI",
1035                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1036                         storeVariant ? " -variant " : "",
1037                         storeVariant ? VariantName(gameInfo.variant) : "");
1038         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1039         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1040         if(insert != q) insert[-1] = NULLCHAR;
1041         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1042         if(q)   free(q);
1043         FloatToFront(&appData.recentEngineList, buf);
1044     }
1045     ReplaceEngine(cps, i);
1046 }
1047
1048 void
1049 InitTimeControls ()
1050 {
1051     int matched, min, sec;
1052     /*
1053      * Parse timeControl resource
1054      */
1055     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1056                           appData.movesPerSession)) {
1057         char buf[MSG_SIZ];
1058         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1059         DisplayFatalError(buf, 0, 2);
1060     }
1061
1062     /*
1063      * Parse searchTime resource
1064      */
1065     if (*appData.searchTime != NULLCHAR) {
1066         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1067         if (matched == 1) {
1068             searchTime = min * 60;
1069         } else if (matched == 2) {
1070             searchTime = min * 60 + sec;
1071         } else {
1072             char buf[MSG_SIZ];
1073             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1074             DisplayFatalError(buf, 0, 2);
1075         }
1076     }
1077 }
1078
1079 void
1080 InitBackEnd1 ()
1081 {
1082
1083     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1084     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1085
1086     GetTimeMark(&programStartTime);
1087     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1088     appData.seedBase = random() + (random()<<15);
1089     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1090
1091     ClearProgramStats();
1092     programStats.ok_to_send = 1;
1093     programStats.seen_stat = 0;
1094
1095     /*
1096      * Initialize game list
1097      */
1098     ListNew(&gameList);
1099
1100
1101     /*
1102      * Internet chess server status
1103      */
1104     if (appData.icsActive) {
1105         appData.matchMode = FALSE;
1106         appData.matchGames = 0;
1107 #if ZIPPY
1108         appData.noChessProgram = !appData.zippyPlay;
1109 #else
1110         appData.zippyPlay = FALSE;
1111         appData.zippyTalk = FALSE;
1112         appData.noChessProgram = TRUE;
1113 #endif
1114         if (*appData.icsHelper != NULLCHAR) {
1115             appData.useTelnet = TRUE;
1116             appData.telnetProgram = appData.icsHelper;
1117         }
1118     } else {
1119         appData.zippyTalk = appData.zippyPlay = FALSE;
1120     }
1121
1122     /* [AS] Initialize pv info list [HGM] and game state */
1123     {
1124         int i, j;
1125
1126         for( i=0; i<=framePtr; i++ ) {
1127             pvInfoList[i].depth = -1;
1128             boards[i][EP_STATUS] = EP_NONE;
1129             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1130         }
1131     }
1132
1133     InitTimeControls();
1134
1135     /* [AS] Adjudication threshold */
1136     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1137
1138     InitEngine(&first, 0);
1139     InitEngine(&second, 1);
1140     CommonEngineInit();
1141
1142     pairing.which = "pairing"; // pairing engine
1143     pairing.pr = NoProc;
1144     pairing.isr = NULL;
1145     pairing.program = appData.pairingEngine;
1146     pairing.host = "localhost";
1147     pairing.dir = ".";
1148
1149     if (appData.icsActive) {
1150         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1151     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1152         appData.clockMode = FALSE;
1153         first.sendTime = second.sendTime = 0;
1154     }
1155
1156 #if ZIPPY
1157     /* Override some settings from environment variables, for backward
1158        compatibility.  Unfortunately it's not feasible to have the env
1159        vars just set defaults, at least in xboard.  Ugh.
1160     */
1161     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1162       ZippyInit();
1163     }
1164 #endif
1165
1166     if (!appData.icsActive) {
1167       char buf[MSG_SIZ];
1168       int len;
1169
1170       /* Check for variants that are supported only in ICS mode,
1171          or not at all.  Some that are accepted here nevertheless
1172          have bugs; see comments below.
1173       */
1174       VariantClass variant = StringToVariant(appData.variant);
1175       switch (variant) {
1176       case VariantBughouse:     /* need four players and two boards */
1177       case VariantKriegspiel:   /* need to hide pieces and move details */
1178         /* case VariantFischeRandom: (Fabien: moved below) */
1179         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1180         if( (len >= MSG_SIZ) && appData.debugMode )
1181           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1182
1183         DisplayFatalError(buf, 0, 2);
1184         return;
1185
1186       case VariantUnknown:
1187       case VariantLoadable:
1188       case Variant29:
1189       case Variant30:
1190       case Variant31:
1191       case Variant32:
1192       case Variant33:
1193       case Variant34:
1194       case Variant35:
1195       case Variant36:
1196       default:
1197         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1198         if( (len >= MSG_SIZ) && appData.debugMode )
1199           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1200
1201         DisplayFatalError(buf, 0, 2);
1202         return;
1203
1204       case VariantNormal:     /* definitely works! */
1205         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1206           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1207           return;
1208         }
1209       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1210       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1211       case VariantGothic:     /* [HGM] should work */
1212       case VariantCapablanca: /* [HGM] should work */
1213       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1214       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1215       case VariantChu:        /* [HGM] experimental */
1216       case VariantKnightmate: /* [HGM] should work */
1217       case VariantCylinder:   /* [HGM] untested */
1218       case VariantFalcon:     /* [HGM] untested */
1219       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1220                                  offboard interposition not understood */
1221       case VariantWildCastle: /* pieces not automatically shuffled */
1222       case VariantNoCastle:   /* pieces not automatically shuffled */
1223       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1224       case VariantLosers:     /* should work except for win condition,
1225                                  and doesn't know captures are mandatory */
1226       case VariantSuicide:    /* should work except for win condition,
1227                                  and doesn't know captures are mandatory */
1228       case VariantGiveaway:   /* should work except for win condition,
1229                                  and doesn't know captures are mandatory */
1230       case VariantTwoKings:   /* should work */
1231       case VariantAtomic:     /* should work except for win condition */
1232       case Variant3Check:     /* should work except for win condition */
1233       case VariantShatranj:   /* should work except for all win conditions */
1234       case VariantMakruk:     /* should work except for draw countdown */
1235       case VariantASEAN :     /* should work except for draw countdown */
1236       case VariantBerolina:   /* might work if TestLegality is off */
1237       case VariantCapaRandom: /* should work */
1238       case VariantJanus:      /* should work */
1239       case VariantSuper:      /* experimental */
1240       case VariantGreat:      /* experimental, requires legality testing to be off */
1241       case VariantSChess:     /* S-Chess, should work */
1242       case VariantGrand:      /* should work */
1243       case VariantSpartan:    /* should work */
1244       case VariantLion:       /* should work */
1245       case VariantChuChess:   /* should work */
1246         break;
1247       }
1248     }
1249
1250 }
1251
1252 int
1253 NextIntegerFromString (char ** str, long * value)
1254 {
1255     int result = -1;
1256     char * s = *str;
1257
1258     while( *s == ' ' || *s == '\t' ) {
1259         s++;
1260     }
1261
1262     *value = 0;
1263
1264     if( *s >= '0' && *s <= '9' ) {
1265         while( *s >= '0' && *s <= '9' ) {
1266             *value = *value * 10 + (*s - '0');
1267             s++;
1268         }
1269
1270         result = 0;
1271     }
1272
1273     *str = s;
1274
1275     return result;
1276 }
1277
1278 int
1279 NextTimeControlFromString (char ** str, long * value)
1280 {
1281     long temp;
1282     int result = NextIntegerFromString( str, &temp );
1283
1284     if( result == 0 ) {
1285         *value = temp * 60; /* Minutes */
1286         if( **str == ':' ) {
1287             (*str)++;
1288             result = NextIntegerFromString( str, &temp );
1289             *value += temp; /* Seconds */
1290         }
1291     }
1292
1293     return result;
1294 }
1295
1296 int
1297 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1298 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1299     int result = -1, type = 0; long temp, temp2;
1300
1301     if(**str != ':') return -1; // old params remain in force!
1302     (*str)++;
1303     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1304     if( NextIntegerFromString( str, &temp ) ) return -1;
1305     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1306
1307     if(**str != '/') {
1308         /* time only: incremental or sudden-death time control */
1309         if(**str == '+') { /* increment follows; read it */
1310             (*str)++;
1311             if(**str == '!') type = *(*str)++; // Bronstein TC
1312             if(result = NextIntegerFromString( str, &temp2)) return -1;
1313             *inc = temp2 * 1000;
1314             if(**str == '.') { // read fraction of increment
1315                 char *start = ++(*str);
1316                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1317                 temp2 *= 1000;
1318                 while(start++ < *str) temp2 /= 10;
1319                 *inc += temp2;
1320             }
1321         } else *inc = 0;
1322         *moves = 0; *tc = temp * 1000; *incType = type;
1323         return 0;
1324     }
1325
1326     (*str)++; /* classical time control */
1327     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1328
1329     if(result == 0) {
1330         *moves = temp;
1331         *tc    = temp2 * 1000;
1332         *inc   = 0;
1333         *incType = type;
1334     }
1335     return result;
1336 }
1337
1338 int
1339 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1340 {   /* [HGM] get time to add from the multi-session time-control string */
1341     int incType, moves=1; /* kludge to force reading of first session */
1342     long time, increment;
1343     char *s = tcString;
1344
1345     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1346     do {
1347         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1348         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1349         if(movenr == -1) return time;    /* last move before new session     */
1350         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1351         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1352         if(!moves) return increment;     /* current session is incremental   */
1353         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1354     } while(movenr >= -1);               /* try again for next session       */
1355
1356     return 0; // no new time quota on this move
1357 }
1358
1359 int
1360 ParseTimeControl (char *tc, float ti, int mps)
1361 {
1362   long tc1;
1363   long tc2;
1364   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1365   int min, sec=0;
1366
1367   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1368   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1369       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1370   if(ti > 0) {
1371
1372     if(mps)
1373       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1374     else
1375       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1376   } else {
1377     if(mps)
1378       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1379     else
1380       snprintf(buf, MSG_SIZ, ":%s", mytc);
1381   }
1382   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1383
1384   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1385     return FALSE;
1386   }
1387
1388   if( *tc == '/' ) {
1389     /* Parse second time control */
1390     tc++;
1391
1392     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1393       return FALSE;
1394     }
1395
1396     if( tc2 == 0 ) {
1397       return FALSE;
1398     }
1399
1400     timeControl_2 = tc2 * 1000;
1401   }
1402   else {
1403     timeControl_2 = 0;
1404   }
1405
1406   if( tc1 == 0 ) {
1407     return FALSE;
1408   }
1409
1410   timeControl = tc1 * 1000;
1411
1412   if (ti >= 0) {
1413     timeIncrement = ti * 1000;  /* convert to ms */
1414     movesPerSession = 0;
1415   } else {
1416     timeIncrement = 0;
1417     movesPerSession = mps;
1418   }
1419   return TRUE;
1420 }
1421
1422 void
1423 InitBackEnd2 ()
1424 {
1425     if (appData.debugMode) {
1426 #    ifdef __GIT_VERSION
1427       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1428 #    else
1429       fprintf(debugFP, "Version: %s\n", programVersion);
1430 #    endif
1431     }
1432     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1433
1434     set_cont_sequence(appData.wrapContSeq);
1435     if (appData.matchGames > 0) {
1436         appData.matchMode = TRUE;
1437     } else if (appData.matchMode) {
1438         appData.matchGames = 1;
1439     }
1440     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1441         appData.matchGames = appData.sameColorGames;
1442     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1443         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1444         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1445     }
1446     Reset(TRUE, FALSE);
1447     if (appData.noChessProgram || first.protocolVersion == 1) {
1448       InitBackEnd3();
1449     } else {
1450       /* kludge: allow timeout for initial "feature" commands */
1451       FreezeUI();
1452       DisplayMessage("", _("Starting chess program"));
1453       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1454     }
1455 }
1456
1457 int
1458 CalculateIndex (int index, int gameNr)
1459 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1460     int res;
1461     if(index > 0) return index; // fixed nmber
1462     if(index == 0) return 1;
1463     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1464     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1465     return res;
1466 }
1467
1468 int
1469 LoadGameOrPosition (int gameNr)
1470 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1471     if (*appData.loadGameFile != NULLCHAR) {
1472         if (!LoadGameFromFile(appData.loadGameFile,
1473                 CalculateIndex(appData.loadGameIndex, gameNr),
1474                               appData.loadGameFile, FALSE)) {
1475             DisplayFatalError(_("Bad game file"), 0, 1);
1476             return 0;
1477         }
1478     } else if (*appData.loadPositionFile != NULLCHAR) {
1479         if (!LoadPositionFromFile(appData.loadPositionFile,
1480                 CalculateIndex(appData.loadPositionIndex, gameNr),
1481                                   appData.loadPositionFile)) {
1482             DisplayFatalError(_("Bad position file"), 0, 1);
1483             return 0;
1484         }
1485     }
1486     return 1;
1487 }
1488
1489 void
1490 ReserveGame (int gameNr, char resChar)
1491 {
1492     FILE *tf = fopen(appData.tourneyFile, "r+");
1493     char *p, *q, c, buf[MSG_SIZ];
1494     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1495     safeStrCpy(buf, lastMsg, MSG_SIZ);
1496     DisplayMessage(_("Pick new game"), "");
1497     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1498     ParseArgsFromFile(tf);
1499     p = q = appData.results;
1500     if(appData.debugMode) {
1501       char *r = appData.participants;
1502       fprintf(debugFP, "results = '%s'\n", p);
1503       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1504       fprintf(debugFP, "\n");
1505     }
1506     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1507     nextGame = q - p;
1508     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1509     safeStrCpy(q, p, strlen(p) + 2);
1510     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1511     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1512     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1513         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1514         q[nextGame] = '*';
1515     }
1516     fseek(tf, -(strlen(p)+4), SEEK_END);
1517     c = fgetc(tf);
1518     if(c != '"') // depending on DOS or Unix line endings we can be one off
1519          fseek(tf, -(strlen(p)+2), SEEK_END);
1520     else fseek(tf, -(strlen(p)+3), SEEK_END);
1521     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1522     DisplayMessage(buf, "");
1523     free(p); appData.results = q;
1524     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1525        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1526       int round = appData.defaultMatchGames * appData.tourneyType;
1527       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1528          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1529         UnloadEngine(&first);  // next game belongs to other pairing;
1530         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1531     }
1532     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1533 }
1534
1535 void
1536 MatchEvent (int mode)
1537 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1538         int dummy;
1539         if(matchMode) { // already in match mode: switch it off
1540             abortMatch = TRUE;
1541             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1542             return;
1543         }
1544 //      if(gameMode != BeginningOfGame) {
1545 //          DisplayError(_("You can only start a match from the initial position."), 0);
1546 //          return;
1547 //      }
1548         abortMatch = FALSE;
1549         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1550         /* Set up machine vs. machine match */
1551         nextGame = 0;
1552         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1553         if(appData.tourneyFile[0]) {
1554             ReserveGame(-1, 0);
1555             if(nextGame > appData.matchGames) {
1556                 char buf[MSG_SIZ];
1557                 if(strchr(appData.results, '*') == NULL) {
1558                     FILE *f;
1559                     appData.tourneyCycles++;
1560                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1561                         fclose(f);
1562                         NextTourneyGame(-1, &dummy);
1563                         ReserveGame(-1, 0);
1564                         if(nextGame <= appData.matchGames) {
1565                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1566                             matchMode = mode;
1567                             ScheduleDelayedEvent(NextMatchGame, 10000);
1568                             return;
1569                         }
1570                     }
1571                 }
1572                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1573                 DisplayError(buf, 0);
1574                 appData.tourneyFile[0] = 0;
1575                 return;
1576             }
1577         } else
1578         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1579             DisplayFatalError(_("Can't have a match with no chess programs"),
1580                               0, 2);
1581             return;
1582         }
1583         matchMode = mode;
1584         matchGame = roundNr = 1;
1585         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1586         NextMatchGame();
1587 }
1588
1589 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1590
1591 void
1592 InitBackEnd3 P((void))
1593 {
1594     GameMode initialMode;
1595     char buf[MSG_SIZ];
1596     int err, len;
1597
1598     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1599        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1600         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1601        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1602        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1603         char c, *q = first.variants, *p = strchr(q, ',');
1604         if(p) *p = NULLCHAR;
1605         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1606             int w, h, s;
1607             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1608                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1609             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1610             Reset(TRUE, FALSE);         // and re-initialize
1611         }
1612         if(p) *p = ',';
1613     }
1614
1615     InitChessProgram(&first, startedFromSetupPosition);
1616
1617     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1618         free(programVersion);
1619         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1620         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1621         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1622     }
1623
1624     if (appData.icsActive) {
1625 #ifdef WIN32
1626         /* [DM] Make a console window if needed [HGM] merged ifs */
1627         ConsoleCreate();
1628 #endif
1629         err = establish();
1630         if (err != 0)
1631           {
1632             if (*appData.icsCommPort != NULLCHAR)
1633               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1634                              appData.icsCommPort);
1635             else
1636               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1637                         appData.icsHost, appData.icsPort);
1638
1639             if( (len >= MSG_SIZ) && appData.debugMode )
1640               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1641
1642             DisplayFatalError(buf, err, 1);
1643             return;
1644         }
1645         SetICSMode();
1646         telnetISR =
1647           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1648         fromUserISR =
1649           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1650         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1651             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1652     } else if (appData.noChessProgram) {
1653         SetNCPMode();
1654     } else {
1655         SetGNUMode();
1656     }
1657
1658     if (*appData.cmailGameName != NULLCHAR) {
1659         SetCmailMode();
1660         OpenLoopback(&cmailPR);
1661         cmailISR =
1662           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1663     }
1664
1665     ThawUI();
1666     DisplayMessage("", "");
1667     if (StrCaseCmp(appData.initialMode, "") == 0) {
1668       initialMode = BeginningOfGame;
1669       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1670         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1671         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1672         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1673         ModeHighlight();
1674       }
1675     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1676       initialMode = TwoMachinesPlay;
1677     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1678       initialMode = AnalyzeFile;
1679     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1680       initialMode = AnalyzeMode;
1681     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1682       initialMode = MachinePlaysWhite;
1683     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1684       initialMode = MachinePlaysBlack;
1685     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1686       initialMode = EditGame;
1687     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1688       initialMode = EditPosition;
1689     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1690       initialMode = Training;
1691     } else {
1692       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1693       if( (len >= MSG_SIZ) && appData.debugMode )
1694         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1695
1696       DisplayFatalError(buf, 0, 2);
1697       return;
1698     }
1699
1700     if (appData.matchMode) {
1701         if(appData.tourneyFile[0]) { // start tourney from command line
1702             FILE *f;
1703             if(f = fopen(appData.tourneyFile, "r")) {
1704                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1705                 fclose(f);
1706                 appData.clockMode = TRUE;
1707                 SetGNUMode();
1708             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1709         }
1710         MatchEvent(TRUE);
1711     } else if (*appData.cmailGameName != NULLCHAR) {
1712         /* Set up cmail mode */
1713         ReloadCmailMsgEvent(TRUE);
1714     } else {
1715         /* Set up other modes */
1716         if (initialMode == AnalyzeFile) {
1717           if (*appData.loadGameFile == NULLCHAR) {
1718             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1719             return;
1720           }
1721         }
1722         if (*appData.loadGameFile != NULLCHAR) {
1723             (void) LoadGameFromFile(appData.loadGameFile,
1724                                     appData.loadGameIndex,
1725                                     appData.loadGameFile, TRUE);
1726         } else if (*appData.loadPositionFile != NULLCHAR) {
1727             (void) LoadPositionFromFile(appData.loadPositionFile,
1728                                         appData.loadPositionIndex,
1729                                         appData.loadPositionFile);
1730             /* [HGM] try to make self-starting even after FEN load */
1731             /* to allow automatic setup of fairy variants with wtm */
1732             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1733                 gameMode = BeginningOfGame;
1734                 setboardSpoiledMachineBlack = 1;
1735             }
1736             /* [HGM] loadPos: make that every new game uses the setup */
1737             /* from file as long as we do not switch variant          */
1738             if(!blackPlaysFirst) {
1739                 startedFromPositionFile = TRUE;
1740                 CopyBoard(filePosition, boards[0]);
1741             }
1742         }
1743         if (initialMode == AnalyzeMode) {
1744           if (appData.noChessProgram) {
1745             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1746             return;
1747           }
1748           if (appData.icsActive) {
1749             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1750             return;
1751           }
1752           AnalyzeModeEvent();
1753         } else if (initialMode == AnalyzeFile) {
1754           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1755           ShowThinkingEvent();
1756           AnalyzeFileEvent();
1757           AnalysisPeriodicEvent(1);
1758         } else if (initialMode == MachinePlaysWhite) {
1759           if (appData.noChessProgram) {
1760             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1761                               0, 2);
1762             return;
1763           }
1764           if (appData.icsActive) {
1765             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1766                               0, 2);
1767             return;
1768           }
1769           MachineWhiteEvent();
1770         } else if (initialMode == MachinePlaysBlack) {
1771           if (appData.noChessProgram) {
1772             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1773                               0, 2);
1774             return;
1775           }
1776           if (appData.icsActive) {
1777             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1778                               0, 2);
1779             return;
1780           }
1781           MachineBlackEvent();
1782         } else if (initialMode == TwoMachinesPlay) {
1783           if (appData.noChessProgram) {
1784             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1785                               0, 2);
1786             return;
1787           }
1788           if (appData.icsActive) {
1789             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1790                               0, 2);
1791             return;
1792           }
1793           TwoMachinesEvent();
1794         } else if (initialMode == EditGame) {
1795           EditGameEvent();
1796         } else if (initialMode == EditPosition) {
1797           EditPositionEvent();
1798         } else if (initialMode == Training) {
1799           if (*appData.loadGameFile == NULLCHAR) {
1800             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1801             return;
1802           }
1803           TrainingEvent();
1804         }
1805     }
1806 }
1807
1808 void
1809 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1810 {
1811     DisplayBook(current+1);
1812
1813     MoveHistorySet( movelist, first, last, current, pvInfoList );
1814
1815     EvalGraphSet( first, last, current, pvInfoList );
1816
1817     MakeEngineOutputTitle();
1818 }
1819
1820 /*
1821  * Establish will establish a contact to a remote host.port.
1822  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1823  *  used to talk to the host.
1824  * Returns 0 if okay, error code if not.
1825  */
1826 int
1827 establish ()
1828 {
1829     char buf[MSG_SIZ];
1830
1831     if (*appData.icsCommPort != NULLCHAR) {
1832         /* Talk to the host through a serial comm port */
1833         return OpenCommPort(appData.icsCommPort, &icsPR);
1834
1835     } else if (*appData.gateway != NULLCHAR) {
1836         if (*appData.remoteShell == NULLCHAR) {
1837             /* Use the rcmd protocol to run telnet program on a gateway host */
1838             snprintf(buf, sizeof(buf), "%s %s %s",
1839                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1840             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1841
1842         } else {
1843             /* Use the rsh program to run telnet program on a gateway host */
1844             if (*appData.remoteUser == NULLCHAR) {
1845                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1846                         appData.gateway, appData.telnetProgram,
1847                         appData.icsHost, appData.icsPort);
1848             } else {
1849                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1850                         appData.remoteShell, appData.gateway,
1851                         appData.remoteUser, appData.telnetProgram,
1852                         appData.icsHost, appData.icsPort);
1853             }
1854             return StartChildProcess(buf, "", &icsPR);
1855
1856         }
1857     } else if (appData.useTelnet) {
1858         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1859
1860     } else {
1861         /* TCP socket interface differs somewhat between
1862            Unix and NT; handle details in the front end.
1863            */
1864         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1865     }
1866 }
1867
1868 void
1869 EscapeExpand (char *p, char *q)
1870 {       // [HGM] initstring: routine to shape up string arguments
1871         while(*p++ = *q++) if(p[-1] == '\\')
1872             switch(*q++) {
1873                 case 'n': p[-1] = '\n'; break;
1874                 case 'r': p[-1] = '\r'; break;
1875                 case 't': p[-1] = '\t'; break;
1876                 case '\\': p[-1] = '\\'; break;
1877                 case 0: *p = 0; return;
1878                 default: p[-1] = q[-1]; break;
1879             }
1880 }
1881
1882 void
1883 show_bytes (FILE *fp, char *buf, int count)
1884 {
1885     while (count--) {
1886         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1887             fprintf(fp, "\\%03o", *buf & 0xff);
1888         } else {
1889             putc(*buf, fp);
1890         }
1891         buf++;
1892     }
1893     fflush(fp);
1894 }
1895
1896 /* Returns an errno value */
1897 int
1898 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1899 {
1900     char buf[8192], *p, *q, *buflim;
1901     int left, newcount, outcount;
1902
1903     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1904         *appData.gateway != NULLCHAR) {
1905         if (appData.debugMode) {
1906             fprintf(debugFP, ">ICS: ");
1907             show_bytes(debugFP, message, count);
1908             fprintf(debugFP, "\n");
1909         }
1910         return OutputToProcess(pr, message, count, outError);
1911     }
1912
1913     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1914     p = message;
1915     q = buf;
1916     left = count;
1917     newcount = 0;
1918     while (left) {
1919         if (q >= buflim) {
1920             if (appData.debugMode) {
1921                 fprintf(debugFP, ">ICS: ");
1922                 show_bytes(debugFP, buf, newcount);
1923                 fprintf(debugFP, "\n");
1924             }
1925             outcount = OutputToProcess(pr, buf, newcount, outError);
1926             if (outcount < newcount) return -1; /* to be sure */
1927             q = buf;
1928             newcount = 0;
1929         }
1930         if (*p == '\n') {
1931             *q++ = '\r';
1932             newcount++;
1933         } else if (((unsigned char) *p) == TN_IAC) {
1934             *q++ = (char) TN_IAC;
1935             newcount ++;
1936         }
1937         *q++ = *p++;
1938         newcount++;
1939         left--;
1940     }
1941     if (appData.debugMode) {
1942         fprintf(debugFP, ">ICS: ");
1943         show_bytes(debugFP, buf, newcount);
1944         fprintf(debugFP, "\n");
1945     }
1946     outcount = OutputToProcess(pr, buf, newcount, outError);
1947     if (outcount < newcount) return -1; /* to be sure */
1948     return count;
1949 }
1950
1951 void
1952 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1953 {
1954     int outError, outCount;
1955     static int gotEof = 0;
1956     static FILE *ini;
1957
1958     /* Pass data read from player on to ICS */
1959     if (count > 0) {
1960         gotEof = 0;
1961         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1962         if (outCount < count) {
1963             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1964         }
1965         if(have_sent_ICS_logon == 2) {
1966           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1967             fprintf(ini, "%s", message);
1968             have_sent_ICS_logon = 3;
1969           } else
1970             have_sent_ICS_logon = 1;
1971         } else if(have_sent_ICS_logon == 3) {
1972             fprintf(ini, "%s", message);
1973             fclose(ini);
1974           have_sent_ICS_logon = 1;
1975         }
1976     } else if (count < 0) {
1977         RemoveInputSource(isr);
1978         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1979     } else if (gotEof++ > 0) {
1980         RemoveInputSource(isr);
1981         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1982     }
1983 }
1984
1985 void
1986 KeepAlive ()
1987 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1988     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1989     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1990     SendToICS("date\n");
1991     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1992 }
1993
1994 /* added routine for printf style output to ics */
1995 void
1996 ics_printf (char *format, ...)
1997 {
1998     char buffer[MSG_SIZ];
1999     va_list args;
2000
2001     va_start(args, format);
2002     vsnprintf(buffer, sizeof(buffer), format, args);
2003     buffer[sizeof(buffer)-1] = '\0';
2004     SendToICS(buffer);
2005     va_end(args);
2006 }
2007
2008 void
2009 SendToICS (char *s)
2010 {
2011     int count, outCount, outError;
2012
2013     if (icsPR == NoProc) return;
2014
2015     count = strlen(s);
2016     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2017     if (outCount < count) {
2018         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2019     }
2020 }
2021
2022 /* This is used for sending logon scripts to the ICS. Sending
2023    without a delay causes problems when using timestamp on ICC
2024    (at least on my machine). */
2025 void
2026 SendToICSDelayed (char *s, long msdelay)
2027 {
2028     int count, outCount, outError;
2029
2030     if (icsPR == NoProc) return;
2031
2032     count = strlen(s);
2033     if (appData.debugMode) {
2034         fprintf(debugFP, ">ICS: ");
2035         show_bytes(debugFP, s, count);
2036         fprintf(debugFP, "\n");
2037     }
2038     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2039                                       msdelay);
2040     if (outCount < count) {
2041         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2042     }
2043 }
2044
2045
2046 /* Remove all highlighting escape sequences in s
2047    Also deletes any suffix starting with '('
2048    */
2049 char *
2050 StripHighlightAndTitle (char *s)
2051 {
2052     static char retbuf[MSG_SIZ];
2053     char *p = retbuf;
2054
2055     while (*s != NULLCHAR) {
2056         while (*s == '\033') {
2057             while (*s != NULLCHAR && !isalpha(*s)) s++;
2058             if (*s != NULLCHAR) s++;
2059         }
2060         while (*s != NULLCHAR && *s != '\033') {
2061             if (*s == '(' || *s == '[') {
2062                 *p = NULLCHAR;
2063                 return retbuf;
2064             }
2065             *p++ = *s++;
2066         }
2067     }
2068     *p = NULLCHAR;
2069     return retbuf;
2070 }
2071
2072 /* Remove all highlighting escape sequences in s */
2073 char *
2074 StripHighlight (char *s)
2075 {
2076     static char retbuf[MSG_SIZ];
2077     char *p = retbuf;
2078
2079     while (*s != NULLCHAR) {
2080         while (*s == '\033') {
2081             while (*s != NULLCHAR && !isalpha(*s)) s++;
2082             if (*s != NULLCHAR) s++;
2083         }
2084         while (*s != NULLCHAR && *s != '\033') {
2085             *p++ = *s++;
2086         }
2087     }
2088     *p = NULLCHAR;
2089     return retbuf;
2090 }
2091
2092 char engineVariant[MSG_SIZ];
2093 char *variantNames[] = VARIANT_NAMES;
2094 char *
2095 VariantName (VariantClass v)
2096 {
2097     if(v == VariantUnknown || *engineVariant) return engineVariant;
2098     return variantNames[v];
2099 }
2100
2101
2102 /* Identify a variant from the strings the chess servers use or the
2103    PGN Variant tag names we use. */
2104 VariantClass
2105 StringToVariant (char *e)
2106 {
2107     char *p;
2108     int wnum = -1;
2109     VariantClass v = VariantNormal;
2110     int i, found = FALSE;
2111     char buf[MSG_SIZ], c;
2112     int len;
2113
2114     if (!e) return v;
2115
2116     /* [HGM] skip over optional board-size prefixes */
2117     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2118         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2119         while( *e++ != '_');
2120     }
2121
2122     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2123         v = VariantNormal;
2124         found = TRUE;
2125     } else
2126     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2127       if (p = StrCaseStr(e, variantNames[i])) {
2128         if(p && i >= VariantShogi && (p != e || isalpha(p[strlen(variantNames[i])]))) continue;
2129         v = (VariantClass) i;
2130         found = TRUE;
2131         break;
2132       }
2133     }
2134
2135     if (!found) {
2136       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2137           || StrCaseStr(e, "wild/fr")
2138           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2139         v = VariantFischeRandom;
2140       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2141                  (i = 1, p = StrCaseStr(e, "w"))) {
2142         p += i;
2143         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2144         if (isdigit(*p)) {
2145           wnum = atoi(p);
2146         } else {
2147           wnum = -1;
2148         }
2149         switch (wnum) {
2150         case 0: /* FICS only, actually */
2151         case 1:
2152           /* Castling legal even if K starts on d-file */
2153           v = VariantWildCastle;
2154           break;
2155         case 2:
2156         case 3:
2157         case 4:
2158           /* Castling illegal even if K & R happen to start in
2159              normal positions. */
2160           v = VariantNoCastle;
2161           break;
2162         case 5:
2163         case 7:
2164         case 8:
2165         case 10:
2166         case 11:
2167         case 12:
2168         case 13:
2169         case 14:
2170         case 15:
2171         case 18:
2172         case 19:
2173           /* Castling legal iff K & R start in normal positions */
2174           v = VariantNormal;
2175           break;
2176         case 6:
2177         case 20:
2178         case 21:
2179           /* Special wilds for position setup; unclear what to do here */
2180           v = VariantLoadable;
2181           break;
2182         case 9:
2183           /* Bizarre ICC game */
2184           v = VariantTwoKings;
2185           break;
2186         case 16:
2187           v = VariantKriegspiel;
2188           break;
2189         case 17:
2190           v = VariantLosers;
2191           break;
2192         case 22:
2193           v = VariantFischeRandom;
2194           break;
2195         case 23:
2196           v = VariantCrazyhouse;
2197           break;
2198         case 24:
2199           v = VariantBughouse;
2200           break;
2201         case 25:
2202           v = Variant3Check;
2203           break;
2204         case 26:
2205           /* Not quite the same as FICS suicide! */
2206           v = VariantGiveaway;
2207           break;
2208         case 27:
2209           v = VariantAtomic;
2210           break;
2211         case 28:
2212           v = VariantShatranj;
2213           break;
2214
2215         /* Temporary names for future ICC types.  The name *will* change in
2216            the next xboard/WinBoard release after ICC defines it. */
2217         case 29:
2218           v = Variant29;
2219           break;
2220         case 30:
2221           v = Variant30;
2222           break;
2223         case 31:
2224           v = Variant31;
2225           break;
2226         case 32:
2227           v = Variant32;
2228           break;
2229         case 33:
2230           v = Variant33;
2231           break;
2232         case 34:
2233           v = Variant34;
2234           break;
2235         case 35:
2236           v = Variant35;
2237           break;
2238         case 36:
2239           v = Variant36;
2240           break;
2241         case 37:
2242           v = VariantShogi;
2243           break;
2244         case 38:
2245           v = VariantXiangqi;
2246           break;
2247         case 39:
2248           v = VariantCourier;
2249           break;
2250         case 40:
2251           v = VariantGothic;
2252           break;
2253         case 41:
2254           v = VariantCapablanca;
2255           break;
2256         case 42:
2257           v = VariantKnightmate;
2258           break;
2259         case 43:
2260           v = VariantFairy;
2261           break;
2262         case 44:
2263           v = VariantCylinder;
2264           break;
2265         case 45:
2266           v = VariantFalcon;
2267           break;
2268         case 46:
2269           v = VariantCapaRandom;
2270           break;
2271         case 47:
2272           v = VariantBerolina;
2273           break;
2274         case 48:
2275           v = VariantJanus;
2276           break;
2277         case 49:
2278           v = VariantSuper;
2279           break;
2280         case 50:
2281           v = VariantGreat;
2282           break;
2283         case -1:
2284           /* Found "wild" or "w" in the string but no number;
2285              must assume it's normal chess. */
2286           v = VariantNormal;
2287           break;
2288         default:
2289           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2290           if( (len >= MSG_SIZ) && appData.debugMode )
2291             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2292
2293           DisplayError(buf, 0);
2294           v = VariantUnknown;
2295           break;
2296         }
2297       }
2298     }
2299     if (appData.debugMode) {
2300       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2301               e, wnum, VariantName(v));
2302     }
2303     return v;
2304 }
2305
2306 static int leftover_start = 0, leftover_len = 0;
2307 char star_match[STAR_MATCH_N][MSG_SIZ];
2308
2309 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2310    advance *index beyond it, and set leftover_start to the new value of
2311    *index; else return FALSE.  If pattern contains the character '*', it
2312    matches any sequence of characters not containing '\r', '\n', or the
2313    character following the '*' (if any), and the matched sequence(s) are
2314    copied into star_match.
2315    */
2316 int
2317 looking_at ( char *buf, int *index, char *pattern)
2318 {
2319     char *bufp = &buf[*index], *patternp = pattern;
2320     int star_count = 0;
2321     char *matchp = star_match[0];
2322
2323     for (;;) {
2324         if (*patternp == NULLCHAR) {
2325             *index = leftover_start = bufp - buf;
2326             *matchp = NULLCHAR;
2327             return TRUE;
2328         }
2329         if (*bufp == NULLCHAR) return FALSE;
2330         if (*patternp == '*') {
2331             if (*bufp == *(patternp + 1)) {
2332                 *matchp = NULLCHAR;
2333                 matchp = star_match[++star_count];
2334                 patternp += 2;
2335                 bufp++;
2336                 continue;
2337             } else if (*bufp == '\n' || *bufp == '\r') {
2338                 patternp++;
2339                 if (*patternp == NULLCHAR)
2340                   continue;
2341                 else
2342                   return FALSE;
2343             } else {
2344                 *matchp++ = *bufp++;
2345                 continue;
2346             }
2347         }
2348         if (*patternp != *bufp) return FALSE;
2349         patternp++;
2350         bufp++;
2351     }
2352 }
2353
2354 void
2355 SendToPlayer (char *data, int length)
2356 {
2357     int error, outCount;
2358     outCount = OutputToProcess(NoProc, data, length, &error);
2359     if (outCount < length) {
2360         DisplayFatalError(_("Error writing to display"), error, 1);
2361     }
2362 }
2363
2364 void
2365 PackHolding (char packed[], char *holding)
2366 {
2367     char *p = holding;
2368     char *q = packed;
2369     int runlength = 0;
2370     int curr = 9999;
2371     do {
2372         if (*p == curr) {
2373             runlength++;
2374         } else {
2375             switch (runlength) {
2376               case 0:
2377                 break;
2378               case 1:
2379                 *q++ = curr;
2380                 break;
2381               case 2:
2382                 *q++ = curr;
2383                 *q++ = curr;
2384                 break;
2385               default:
2386                 sprintf(q, "%d", runlength);
2387                 while (*q) q++;
2388                 *q++ = curr;
2389                 break;
2390             }
2391             runlength = 1;
2392             curr = *p;
2393         }
2394     } while (*p++);
2395     *q = NULLCHAR;
2396 }
2397
2398 /* Telnet protocol requests from the front end */
2399 void
2400 TelnetRequest (unsigned char ddww, unsigned char option)
2401 {
2402     unsigned char msg[3];
2403     int outCount, outError;
2404
2405     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2406
2407     if (appData.debugMode) {
2408         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2409         switch (ddww) {
2410           case TN_DO:
2411             ddwwStr = "DO";
2412             break;
2413           case TN_DONT:
2414             ddwwStr = "DONT";
2415             break;
2416           case TN_WILL:
2417             ddwwStr = "WILL";
2418             break;
2419           case TN_WONT:
2420             ddwwStr = "WONT";
2421             break;
2422           default:
2423             ddwwStr = buf1;
2424             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2425             break;
2426         }
2427         switch (option) {
2428           case TN_ECHO:
2429             optionStr = "ECHO";
2430             break;
2431           default:
2432             optionStr = buf2;
2433             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2434             break;
2435         }
2436         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2437     }
2438     msg[0] = TN_IAC;
2439     msg[1] = ddww;
2440     msg[2] = option;
2441     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2442     if (outCount < 3) {
2443         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2444     }
2445 }
2446
2447 void
2448 DoEcho ()
2449 {
2450     if (!appData.icsActive) return;
2451     TelnetRequest(TN_DO, TN_ECHO);
2452 }
2453
2454 void
2455 DontEcho ()
2456 {
2457     if (!appData.icsActive) return;
2458     TelnetRequest(TN_DONT, TN_ECHO);
2459 }
2460
2461 void
2462 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2463 {
2464     /* put the holdings sent to us by the server on the board holdings area */
2465     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2466     char p;
2467     ChessSquare piece;
2468
2469     if(gameInfo.holdingsWidth < 2)  return;
2470     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2471         return; // prevent overwriting by pre-board holdings
2472
2473     if( (int)lowestPiece >= BlackPawn ) {
2474         holdingsColumn = 0;
2475         countsColumn = 1;
2476         holdingsStartRow = BOARD_HEIGHT-1;
2477         direction = -1;
2478     } else {
2479         holdingsColumn = BOARD_WIDTH-1;
2480         countsColumn = BOARD_WIDTH-2;
2481         holdingsStartRow = 0;
2482         direction = 1;
2483     }
2484
2485     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2486         board[i][holdingsColumn] = EmptySquare;
2487         board[i][countsColumn]   = (ChessSquare) 0;
2488     }
2489     while( (p=*holdings++) != NULLCHAR ) {
2490         piece = CharToPiece( ToUpper(p) );
2491         if(piece == EmptySquare) continue;
2492         /*j = (int) piece - (int) WhitePawn;*/
2493         j = PieceToNumber(piece);
2494         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2495         if(j < 0) continue;               /* should not happen */
2496         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2497         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2498         board[holdingsStartRow+j*direction][countsColumn]++;
2499     }
2500 }
2501
2502
2503 void
2504 VariantSwitch (Board board, VariantClass newVariant)
2505 {
2506    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2507    static Board oldBoard;
2508
2509    startedFromPositionFile = FALSE;
2510    if(gameInfo.variant == newVariant) return;
2511
2512    /* [HGM] This routine is called each time an assignment is made to
2513     * gameInfo.variant during a game, to make sure the board sizes
2514     * are set to match the new variant. If that means adding or deleting
2515     * holdings, we shift the playing board accordingly
2516     * This kludge is needed because in ICS observe mode, we get boards
2517     * of an ongoing game without knowing the variant, and learn about the
2518     * latter only later. This can be because of the move list we requested,
2519     * in which case the game history is refilled from the beginning anyway,
2520     * but also when receiving holdings of a crazyhouse game. In the latter
2521     * case we want to add those holdings to the already received position.
2522     */
2523
2524
2525    if (appData.debugMode) {
2526      fprintf(debugFP, "Switch board from %s to %s\n",
2527              VariantName(gameInfo.variant), VariantName(newVariant));
2528      setbuf(debugFP, NULL);
2529    }
2530    shuffleOpenings = 0;       /* [HGM] shuffle */
2531    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2532    switch(newVariant)
2533      {
2534      case VariantShogi:
2535        newWidth = 9;  newHeight = 9;
2536        gameInfo.holdingsSize = 7;
2537      case VariantBughouse:
2538      case VariantCrazyhouse:
2539        newHoldingsWidth = 2; break;
2540      case VariantGreat:
2541        newWidth = 10;
2542      case VariantSuper:
2543        newHoldingsWidth = 2;
2544        gameInfo.holdingsSize = 8;
2545        break;
2546      case VariantGothic:
2547      case VariantCapablanca:
2548      case VariantCapaRandom:
2549        newWidth = 10;
2550      default:
2551        newHoldingsWidth = gameInfo.holdingsSize = 0;
2552      };
2553
2554    if(newWidth  != gameInfo.boardWidth  ||
2555       newHeight != gameInfo.boardHeight ||
2556       newHoldingsWidth != gameInfo.holdingsWidth ) {
2557
2558      /* shift position to new playing area, if needed */
2559      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2560        for(i=0; i<BOARD_HEIGHT; i++)
2561          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2562            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2563              board[i][j];
2564        for(i=0; i<newHeight; i++) {
2565          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2566          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2567        }
2568      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2569        for(i=0; i<BOARD_HEIGHT; i++)
2570          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2571            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2572              board[i][j];
2573      }
2574      board[HOLDINGS_SET] = 0;
2575      gameInfo.boardWidth  = newWidth;
2576      gameInfo.boardHeight = newHeight;
2577      gameInfo.holdingsWidth = newHoldingsWidth;
2578      gameInfo.variant = newVariant;
2579      InitDrawingSizes(-2, 0);
2580    } else gameInfo.variant = newVariant;
2581    CopyBoard(oldBoard, board);   // remember correctly formatted board
2582      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2583    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2584 }
2585
2586 static int loggedOn = FALSE;
2587
2588 /*-- Game start info cache: --*/
2589 int gs_gamenum;
2590 char gs_kind[MSG_SIZ];
2591 static char player1Name[128] = "";
2592 static char player2Name[128] = "";
2593 static char cont_seq[] = "\n\\   ";
2594 static int player1Rating = -1;
2595 static int player2Rating = -1;
2596 /*----------------------------*/
2597
2598 ColorClass curColor = ColorNormal;
2599 int suppressKibitz = 0;
2600
2601 // [HGM] seekgraph
2602 Boolean soughtPending = FALSE;
2603 Boolean seekGraphUp;
2604 #define MAX_SEEK_ADS 200
2605 #define SQUARE 0x80
2606 char *seekAdList[MAX_SEEK_ADS];
2607 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2608 float tcList[MAX_SEEK_ADS];
2609 char colorList[MAX_SEEK_ADS];
2610 int nrOfSeekAds = 0;
2611 int minRating = 1010, maxRating = 2800;
2612 int hMargin = 10, vMargin = 20, h, w;
2613 extern int squareSize, lineGap;
2614
2615 void
2616 PlotSeekAd (int i)
2617 {
2618         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2619         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2620         if(r < minRating+100 && r >=0 ) r = minRating+100;
2621         if(r > maxRating) r = maxRating;
2622         if(tc < 1.f) tc = 1.f;
2623         if(tc > 95.f) tc = 95.f;
2624         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2625         y = ((double)r - minRating)/(maxRating - minRating)
2626             * (h-vMargin-squareSize/8-1) + vMargin;
2627         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2628         if(strstr(seekAdList[i], " u ")) color = 1;
2629         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2630            !strstr(seekAdList[i], "bullet") &&
2631            !strstr(seekAdList[i], "blitz") &&
2632            !strstr(seekAdList[i], "standard") ) color = 2;
2633         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2634         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2635 }
2636
2637 void
2638 PlotSingleSeekAd (int i)
2639 {
2640         PlotSeekAd(i);
2641 }
2642
2643 void
2644 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2645 {
2646         char buf[MSG_SIZ], *ext = "";
2647         VariantClass v = StringToVariant(type);
2648         if(strstr(type, "wild")) {
2649             ext = type + 4; // append wild number
2650             if(v == VariantFischeRandom) type = "chess960"; else
2651             if(v == VariantLoadable) type = "setup"; else
2652             type = VariantName(v);
2653         }
2654         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2655         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2656             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2657             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2658             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2659             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2660             seekNrList[nrOfSeekAds] = nr;
2661             zList[nrOfSeekAds] = 0;
2662             seekAdList[nrOfSeekAds++] = StrSave(buf);
2663             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2664         }
2665 }
2666
2667 void
2668 EraseSeekDot (int i)
2669 {
2670     int x = xList[i], y = yList[i], d=squareSize/4, k;
2671     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2672     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2673     // now replot every dot that overlapped
2674     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2675         int xx = xList[k], yy = yList[k];
2676         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2677             DrawSeekDot(xx, yy, colorList[k]);
2678     }
2679 }
2680
2681 void
2682 RemoveSeekAd (int nr)
2683 {
2684         int i;
2685         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2686             EraseSeekDot(i);
2687             if(seekAdList[i]) free(seekAdList[i]);
2688             seekAdList[i] = seekAdList[--nrOfSeekAds];
2689             seekNrList[i] = seekNrList[nrOfSeekAds];
2690             ratingList[i] = ratingList[nrOfSeekAds];
2691             colorList[i]  = colorList[nrOfSeekAds];
2692             tcList[i] = tcList[nrOfSeekAds];
2693             xList[i]  = xList[nrOfSeekAds];
2694             yList[i]  = yList[nrOfSeekAds];
2695             zList[i]  = zList[nrOfSeekAds];
2696             seekAdList[nrOfSeekAds] = NULL;
2697             break;
2698         }
2699 }
2700
2701 Boolean
2702 MatchSoughtLine (char *line)
2703 {
2704     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2705     int nr, base, inc, u=0; char dummy;
2706
2707     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2708        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2709        (u=1) &&
2710        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2711         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2712         // match: compact and save the line
2713         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2714         return TRUE;
2715     }
2716     return FALSE;
2717 }
2718
2719 int
2720 DrawSeekGraph ()
2721 {
2722     int i;
2723     if(!seekGraphUp) return FALSE;
2724     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2725     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2726
2727     DrawSeekBackground(0, 0, w, h);
2728     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2729     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2730     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2731         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2732         yy = h-1-yy;
2733         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2734         if(i%500 == 0) {
2735             char buf[MSG_SIZ];
2736             snprintf(buf, MSG_SIZ, "%d", i);
2737             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2738         }
2739     }
2740     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2741     for(i=1; i<100; i+=(i<10?1:5)) {
2742         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2743         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2744         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2745             char buf[MSG_SIZ];
2746             snprintf(buf, MSG_SIZ, "%d", i);
2747             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2748         }
2749     }
2750     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2751     return TRUE;
2752 }
2753
2754 int
2755 SeekGraphClick (ClickType click, int x, int y, int moving)
2756 {
2757     static int lastDown = 0, displayed = 0, lastSecond;
2758     if(y < 0) return FALSE;
2759     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2760         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2761         if(!seekGraphUp) return FALSE;
2762         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2763         DrawPosition(TRUE, NULL);
2764         return TRUE;
2765     }
2766     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2767         if(click == Release || moving) return FALSE;
2768         nrOfSeekAds = 0;
2769         soughtPending = TRUE;
2770         SendToICS(ics_prefix);
2771         SendToICS("sought\n"); // should this be "sought all"?
2772     } else { // issue challenge based on clicked ad
2773         int dist = 10000; int i, closest = 0, second = 0;
2774         for(i=0; i<nrOfSeekAds; i++) {
2775             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2776             if(d < dist) { dist = d; closest = i; }
2777             second += (d - zList[i] < 120); // count in-range ads
2778             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2779         }
2780         if(dist < 120) {
2781             char buf[MSG_SIZ];
2782             second = (second > 1);
2783             if(displayed != closest || second != lastSecond) {
2784                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2785                 lastSecond = second; displayed = closest;
2786             }
2787             if(click == Press) {
2788                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2789                 lastDown = closest;
2790                 return TRUE;
2791             } // on press 'hit', only show info
2792             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2793             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2794             SendToICS(ics_prefix);
2795             SendToICS(buf);
2796             return TRUE; // let incoming board of started game pop down the graph
2797         } else if(click == Release) { // release 'miss' is ignored
2798             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2799             if(moving == 2) { // right up-click
2800                 nrOfSeekAds = 0; // refresh graph
2801                 soughtPending = TRUE;
2802                 SendToICS(ics_prefix);
2803                 SendToICS("sought\n"); // should this be "sought all"?
2804             }
2805             return TRUE;
2806         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2807         // press miss or release hit 'pop down' seek graph
2808         seekGraphUp = FALSE;
2809         DrawPosition(TRUE, NULL);
2810     }
2811     return TRUE;
2812 }
2813
2814 void
2815 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2816 {
2817 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2818 #define STARTED_NONE 0
2819 #define STARTED_MOVES 1
2820 #define STARTED_BOARD 2
2821 #define STARTED_OBSERVE 3
2822 #define STARTED_HOLDINGS 4
2823 #define STARTED_CHATTER 5
2824 #define STARTED_COMMENT 6
2825 #define STARTED_MOVES_NOHIDE 7
2826
2827     static int started = STARTED_NONE;
2828     static char parse[20000];
2829     static int parse_pos = 0;
2830     static char buf[BUF_SIZE + 1];
2831     static int firstTime = TRUE, intfSet = FALSE;
2832     static ColorClass prevColor = ColorNormal;
2833     static int savingComment = FALSE;
2834     static int cmatch = 0; // continuation sequence match
2835     char *bp;
2836     char str[MSG_SIZ];
2837     int i, oldi;
2838     int buf_len;
2839     int next_out;
2840     int tkind;
2841     int backup;    /* [DM] For zippy color lines */
2842     char *p;
2843     char talker[MSG_SIZ]; // [HGM] chat
2844     int channel, collective=0;
2845
2846     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2847
2848     if (appData.debugMode) {
2849       if (!error) {
2850         fprintf(debugFP, "<ICS: ");
2851         show_bytes(debugFP, data, count);
2852         fprintf(debugFP, "\n");
2853       }
2854     }
2855
2856     if (appData.debugMode) { int f = forwardMostMove;
2857         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2858                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2859                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2860     }
2861     if (count > 0) {
2862         /* If last read ended with a partial line that we couldn't parse,
2863            prepend it to the new read and try again. */
2864         if (leftover_len > 0) {
2865             for (i=0; i<leftover_len; i++)
2866               buf[i] = buf[leftover_start + i];
2867         }
2868
2869     /* copy new characters into the buffer */
2870     bp = buf + leftover_len;
2871     buf_len=leftover_len;
2872     for (i=0; i<count; i++)
2873     {
2874         // ignore these
2875         if (data[i] == '\r')
2876             continue;
2877
2878         // join lines split by ICS?
2879         if (!appData.noJoin)
2880         {
2881             /*
2882                 Joining just consists of finding matches against the
2883                 continuation sequence, and discarding that sequence
2884                 if found instead of copying it.  So, until a match
2885                 fails, there's nothing to do since it might be the
2886                 complete sequence, and thus, something we don't want
2887                 copied.
2888             */
2889             if (data[i] == cont_seq[cmatch])
2890             {
2891                 cmatch++;
2892                 if (cmatch == strlen(cont_seq))
2893                 {
2894                     cmatch = 0; // complete match.  just reset the counter
2895
2896                     /*
2897                         it's possible for the ICS to not include the space
2898                         at the end of the last word, making our [correct]
2899                         join operation fuse two separate words.  the server
2900                         does this when the space occurs at the width setting.
2901                     */
2902                     if (!buf_len || buf[buf_len-1] != ' ')
2903                     {
2904                         *bp++ = ' ';
2905                         buf_len++;
2906                     }
2907                 }
2908                 continue;
2909             }
2910             else if (cmatch)
2911             {
2912                 /*
2913                     match failed, so we have to copy what matched before
2914                     falling through and copying this character.  In reality,
2915                     this will only ever be just the newline character, but
2916                     it doesn't hurt to be precise.
2917                 */
2918                 strncpy(bp, cont_seq, cmatch);
2919                 bp += cmatch;
2920                 buf_len += cmatch;
2921                 cmatch = 0;
2922             }
2923         }
2924
2925         // copy this char
2926         *bp++ = data[i];
2927         buf_len++;
2928     }
2929
2930         buf[buf_len] = NULLCHAR;
2931 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2932         next_out = 0;
2933         leftover_start = 0;
2934
2935         i = 0;
2936         while (i < buf_len) {
2937             /* Deal with part of the TELNET option negotiation
2938                protocol.  We refuse to do anything beyond the
2939                defaults, except that we allow the WILL ECHO option,
2940                which ICS uses to turn off password echoing when we are
2941                directly connected to it.  We reject this option
2942                if localLineEditing mode is on (always on in xboard)
2943                and we are talking to port 23, which might be a real
2944                telnet server that will try to keep WILL ECHO on permanently.
2945              */
2946             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2947                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2948                 unsigned char option;
2949                 oldi = i;
2950                 switch ((unsigned char) buf[++i]) {
2951                   case TN_WILL:
2952                     if (appData.debugMode)
2953                       fprintf(debugFP, "\n<WILL ");
2954                     switch (option = (unsigned char) buf[++i]) {
2955                       case TN_ECHO:
2956                         if (appData.debugMode)
2957                           fprintf(debugFP, "ECHO ");
2958                         /* Reply only if this is a change, according
2959                            to the protocol rules. */
2960                         if (remoteEchoOption) break;
2961                         if (appData.localLineEditing &&
2962                             atoi(appData.icsPort) == TN_PORT) {
2963                             TelnetRequest(TN_DONT, TN_ECHO);
2964                         } else {
2965                             EchoOff();
2966                             TelnetRequest(TN_DO, TN_ECHO);
2967                             remoteEchoOption = TRUE;
2968                         }
2969                         break;
2970                       default:
2971                         if (appData.debugMode)
2972                           fprintf(debugFP, "%d ", option);
2973                         /* Whatever this is, we don't want it. */
2974                         TelnetRequest(TN_DONT, option);
2975                         break;
2976                     }
2977                     break;
2978                   case TN_WONT:
2979                     if (appData.debugMode)
2980                       fprintf(debugFP, "\n<WONT ");
2981                     switch (option = (unsigned char) buf[++i]) {
2982                       case TN_ECHO:
2983                         if (appData.debugMode)
2984                           fprintf(debugFP, "ECHO ");
2985                         /* Reply only if this is a change, according
2986                            to the protocol rules. */
2987                         if (!remoteEchoOption) break;
2988                         EchoOn();
2989                         TelnetRequest(TN_DONT, TN_ECHO);
2990                         remoteEchoOption = FALSE;
2991                         break;
2992                       default:
2993                         if (appData.debugMode)
2994                           fprintf(debugFP, "%d ", (unsigned char) option);
2995                         /* Whatever this is, it must already be turned
2996                            off, because we never agree to turn on
2997                            anything non-default, so according to the
2998                            protocol rules, we don't reply. */
2999                         break;
3000                     }
3001                     break;
3002                   case TN_DO:
3003                     if (appData.debugMode)
3004                       fprintf(debugFP, "\n<DO ");
3005                     switch (option = (unsigned char) buf[++i]) {
3006                       default:
3007                         /* Whatever this is, we refuse to do it. */
3008                         if (appData.debugMode)
3009                           fprintf(debugFP, "%d ", option);
3010                         TelnetRequest(TN_WONT, option);
3011                         break;
3012                     }
3013                     break;
3014                   case TN_DONT:
3015                     if (appData.debugMode)
3016                       fprintf(debugFP, "\n<DONT ");
3017                     switch (option = (unsigned char) buf[++i]) {
3018                       default:
3019                         if (appData.debugMode)
3020                           fprintf(debugFP, "%d ", option);
3021                         /* Whatever this is, we are already not doing
3022                            it, because we never agree to do anything
3023                            non-default, so according to the protocol
3024                            rules, we don't reply. */
3025                         break;
3026                     }
3027                     break;
3028                   case TN_IAC:
3029                     if (appData.debugMode)
3030                       fprintf(debugFP, "\n<IAC ");
3031                     /* Doubled IAC; pass it through */
3032                     i--;
3033                     break;
3034                   default:
3035                     if (appData.debugMode)
3036                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3037                     /* Drop all other telnet commands on the floor */
3038                     break;
3039                 }
3040                 if (oldi > next_out)
3041                   SendToPlayer(&buf[next_out], oldi - next_out);
3042                 if (++i > next_out)
3043                   next_out = i;
3044                 continue;
3045             }
3046
3047             /* OK, this at least will *usually* work */
3048             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3049                 loggedOn = TRUE;
3050             }
3051
3052             if (loggedOn && !intfSet) {
3053                 if (ics_type == ICS_ICC) {
3054                   snprintf(str, MSG_SIZ,
3055                           "/set-quietly interface %s\n/set-quietly style 12\n",
3056                           programVersion);
3057                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3058                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3059                 } else if (ics_type == ICS_CHESSNET) {
3060                   snprintf(str, MSG_SIZ, "/style 12\n");
3061                 } else {
3062                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3063                   strcat(str, programVersion);
3064                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3065                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3066                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3067 #ifdef WIN32
3068                   strcat(str, "$iset nohighlight 1\n");
3069 #endif
3070                   strcat(str, "$iset lock 1\n$style 12\n");
3071                 }
3072                 SendToICS(str);
3073                 NotifyFrontendLogin();
3074                 intfSet = TRUE;
3075             }
3076
3077             if (started == STARTED_COMMENT) {
3078                 /* Accumulate characters in comment */
3079                 parse[parse_pos++] = buf[i];
3080                 if (buf[i] == '\n') {
3081                     parse[parse_pos] = NULLCHAR;
3082                     if(chattingPartner>=0) {
3083                         char mess[MSG_SIZ];
3084                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3085                         OutputChatMessage(chattingPartner, mess);
3086                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3087                             int p;
3088                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3089                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3090                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3091                                 OutputChatMessage(p, mess);
3092                                 break;
3093                             }
3094                         }
3095                         chattingPartner = -1;
3096                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3097                         collective = 0;
3098                     } else
3099                     if(!suppressKibitz) // [HGM] kibitz
3100                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3101                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3102                         int nrDigit = 0, nrAlph = 0, j;
3103                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3104                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3105                         parse[parse_pos] = NULLCHAR;
3106                         // try to be smart: if it does not look like search info, it should go to
3107                         // ICS interaction window after all, not to engine-output window.
3108                         for(j=0; j<parse_pos; j++) { // count letters and digits
3109                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3110                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3111                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3112                         }
3113                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3114                             int depth=0; float score;
3115                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3116                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3117                                 pvInfoList[forwardMostMove-1].depth = depth;
3118                                 pvInfoList[forwardMostMove-1].score = 100*score;
3119                             }
3120                             OutputKibitz(suppressKibitz, parse);
3121                         } else {
3122                             char tmp[MSG_SIZ];
3123                             if(gameMode == IcsObserving) // restore original ICS messages
3124                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3125                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3126                             else
3127                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3128                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3129                             SendToPlayer(tmp, strlen(tmp));
3130                         }
3131                         next_out = i+1; // [HGM] suppress printing in ICS window
3132                     }
3133                     started = STARTED_NONE;
3134                 } else {
3135                     /* Don't match patterns against characters in comment */
3136                     i++;
3137                     continue;
3138                 }
3139             }
3140             if (started == STARTED_CHATTER) {
3141                 if (buf[i] != '\n') {
3142                     /* Don't match patterns against characters in chatter */
3143                     i++;
3144                     continue;
3145                 }
3146                 started = STARTED_NONE;
3147                 if(suppressKibitz) next_out = i+1;
3148             }
3149
3150             /* Kludge to deal with rcmd protocol */
3151             if (firstTime && looking_at(buf, &i, "\001*")) {
3152                 DisplayFatalError(&buf[1], 0, 1);
3153                 continue;
3154             } else {
3155                 firstTime = FALSE;
3156             }
3157
3158             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3159                 ics_type = ICS_ICC;
3160                 ics_prefix = "/";
3161                 if (appData.debugMode)
3162                   fprintf(debugFP, "ics_type %d\n", ics_type);
3163                 continue;
3164             }
3165             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3166                 ics_type = ICS_FICS;
3167                 ics_prefix = "$";
3168                 if (appData.debugMode)
3169                   fprintf(debugFP, "ics_type %d\n", ics_type);
3170                 continue;
3171             }
3172             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3173                 ics_type = ICS_CHESSNET;
3174                 ics_prefix = "/";
3175                 if (appData.debugMode)
3176                   fprintf(debugFP, "ics_type %d\n", ics_type);
3177                 continue;
3178             }
3179
3180             if (!loggedOn &&
3181                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3182                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3183                  looking_at(buf, &i, "will be \"*\""))) {
3184               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3185               continue;
3186             }
3187
3188             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3189               char buf[MSG_SIZ];
3190               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3191               DisplayIcsInteractionTitle(buf);
3192               have_set_title = TRUE;
3193             }
3194
3195             /* skip finger notes */
3196             if (started == STARTED_NONE &&
3197                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3198                  (buf[i] == '1' && buf[i+1] == '0')) &&
3199                 buf[i+2] == ':' && buf[i+3] == ' ') {
3200               started = STARTED_CHATTER;
3201               i += 3;
3202               continue;
3203             }
3204
3205             oldi = i;
3206             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3207             if(appData.seekGraph) {
3208                 if(soughtPending && MatchSoughtLine(buf+i)) {
3209                     i = strstr(buf+i, "rated") - buf;
3210                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3211                     next_out = leftover_start = i;
3212                     started = STARTED_CHATTER;
3213                     suppressKibitz = TRUE;
3214                     continue;
3215                 }
3216                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3217                         && looking_at(buf, &i, "* ads displayed")) {
3218                     soughtPending = FALSE;
3219                     seekGraphUp = TRUE;
3220                     DrawSeekGraph();
3221                     continue;
3222                 }
3223                 if(appData.autoRefresh) {
3224                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3225                         int s = (ics_type == ICS_ICC); // ICC format differs
3226                         if(seekGraphUp)
3227                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3228                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3229                         looking_at(buf, &i, "*% "); // eat prompt
3230                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3231                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3232                         next_out = i; // suppress
3233                         continue;
3234                     }
3235                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3236                         char *p = star_match[0];
3237                         while(*p) {
3238                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3239                             while(*p && *p++ != ' '); // next
3240                         }
3241                         looking_at(buf, &i, "*% "); // eat prompt
3242                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3243                         next_out = i;
3244                         continue;
3245                     }
3246                 }
3247             }
3248
3249             /* skip formula vars */
3250             if (started == STARTED_NONE &&
3251                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3252               started = STARTED_CHATTER;
3253               i += 3;
3254               continue;
3255             }
3256
3257             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3258             if (appData.autoKibitz && started == STARTED_NONE &&
3259                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3260                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3261                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3262                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3263                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3264                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3265                         suppressKibitz = TRUE;
3266                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3267                         next_out = i;
3268                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3269                                 && (gameMode == IcsPlayingWhite)) ||
3270                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3271                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3272                             started = STARTED_CHATTER; // own kibitz we simply discard
3273                         else {
3274                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3275                             parse_pos = 0; parse[0] = NULLCHAR;
3276                             savingComment = TRUE;
3277                             suppressKibitz = gameMode != IcsObserving ? 2 :
3278                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3279                         }
3280                         continue;
3281                 } else
3282                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3283                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3284                          && atoi(star_match[0])) {
3285                     // suppress the acknowledgements of our own autoKibitz
3286                     char *p;
3287                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3288                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3289                     SendToPlayer(star_match[0], strlen(star_match[0]));
3290                     if(looking_at(buf, &i, "*% ")) // eat prompt
3291                         suppressKibitz = FALSE;
3292                     next_out = i;
3293                     continue;
3294                 }
3295             } // [HGM] kibitz: end of patch
3296
3297             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3298
3299             // [HGM] chat: intercept tells by users for which we have an open chat window
3300             channel = -1;
3301             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3302                                            looking_at(buf, &i, "* whispers:") ||
3303                                            looking_at(buf, &i, "* kibitzes:") ||
3304                                            looking_at(buf, &i, "* shouts:") ||
3305                                            looking_at(buf, &i, "* c-shouts:") ||
3306                                            looking_at(buf, &i, "--> * ") ||
3307                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3308                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3309                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3310                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3311                 int p;
3312                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3313                 chattingPartner = -1; collective = 0;
3314
3315                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3316                 for(p=0; p<MAX_CHAT; p++) {
3317                     collective = 1;
3318                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3319                     talker[0] = '['; strcat(talker, "] ");
3320                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3321                     chattingPartner = p; break;
3322                     }
3323                 } else
3324                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3325                 for(p=0; p<MAX_CHAT; p++) {
3326                     collective = 1;
3327                     if(!strcmp("kibitzes", chatPartner[p])) {
3328                         talker[0] = '['; strcat(talker, "] ");
3329                         chattingPartner = p; break;
3330                     }
3331                 } else
3332                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3333                 for(p=0; p<MAX_CHAT; p++) {
3334                     collective = 1;
3335                     if(!strcmp("whispers", chatPartner[p])) {
3336                         talker[0] = '['; strcat(talker, "] ");
3337                         chattingPartner = p; break;
3338                     }
3339                 } else
3340                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3341                   if(buf[i-8] == '-' && buf[i-3] == 't')
3342                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3343                     collective = 1;
3344                     if(!strcmp("c-shouts", chatPartner[p])) {
3345                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3346                         chattingPartner = p; break;
3347                     }
3348                   }
3349                   if(chattingPartner < 0)
3350                   for(p=0; p<MAX_CHAT; p++) {
3351                     collective = 1;
3352                     if(!strcmp("shouts", chatPartner[p])) {
3353                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3354                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3355                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3356                         chattingPartner = p; break;
3357                     }
3358                   }
3359                 }
3360                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3361                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3362                     talker[0] = 0;
3363                     Colorize(ColorTell, FALSE);
3364                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3365                     collective |= 2;
3366                     chattingPartner = p; break;
3367                 }
3368                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3369                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3370                     started = STARTED_COMMENT;
3371                     parse_pos = 0; parse[0] = NULLCHAR;
3372                     savingComment = 3 + chattingPartner; // counts as TRUE
3373                     if(collective == 3) i = oldi; else {
3374                         suppressKibitz = TRUE;
3375                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3376                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3377                         continue;
3378                     }
3379                 }
3380             } // [HGM] chat: end of patch
3381
3382           backup = i;
3383             if (appData.zippyTalk || appData.zippyPlay) {
3384                 /* [DM] Backup address for color zippy lines */
3385 #if ZIPPY
3386                if (loggedOn == TRUE)
3387                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3388                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3389 #endif
3390             } // [DM] 'else { ' deleted
3391                 if (
3392                     /* Regular tells and says */
3393                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3394                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3395                     looking_at(buf, &i, "* says: ") ||
3396                     /* Don't color "message" or "messages" output */
3397                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3398                     looking_at(buf, &i, "*. * at *:*: ") ||
3399                     looking_at(buf, &i, "--* (*:*): ") ||
3400                     /* Message notifications (same color as tells) */
3401                     looking_at(buf, &i, "* has left a message ") ||
3402                     looking_at(buf, &i, "* just sent you a message:\n") ||
3403                     /* Whispers and kibitzes */
3404                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3405                     looking_at(buf, &i, "* kibitzes: ") ||
3406                     /* Channel tells */
3407                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3408
3409                   if (tkind == 1 && strchr(star_match[0], ':')) {
3410                       /* Avoid "tells you:" spoofs in channels */
3411                      tkind = 3;
3412                   }
3413                   if (star_match[0][0] == NULLCHAR ||
3414                       strchr(star_match[0], ' ') ||
3415                       (tkind == 3 && strchr(star_match[1], ' '))) {
3416                     /* Reject bogus matches */
3417                     i = oldi;
3418                   } else {
3419                     if (appData.colorize) {
3420                       if (oldi > next_out) {
3421                         SendToPlayer(&buf[next_out], oldi - next_out);
3422                         next_out = oldi;
3423                       }
3424                       switch (tkind) {
3425                       case 1:
3426                         Colorize(ColorTell, FALSE);
3427                         curColor = ColorTell;
3428                         break;
3429                       case 2:
3430                         Colorize(ColorKibitz, FALSE);
3431                         curColor = ColorKibitz;
3432                         break;
3433                       case 3:
3434                         p = strrchr(star_match[1], '(');
3435                         if (p == NULL) {
3436                           p = star_match[1];
3437                         } else {
3438                           p++;
3439                         }
3440                         if (atoi(p) == 1) {
3441                           Colorize(ColorChannel1, FALSE);
3442                           curColor = ColorChannel1;
3443                         } else {
3444                           Colorize(ColorChannel, FALSE);
3445                           curColor = ColorChannel;
3446                         }
3447                         break;
3448                       case 5:
3449                         curColor = ColorNormal;
3450                         break;
3451                       }
3452                     }
3453                     if (started == STARTED_NONE && appData.autoComment &&
3454                         (gameMode == IcsObserving ||
3455                          gameMode == IcsPlayingWhite ||
3456                          gameMode == IcsPlayingBlack)) {
3457                       parse_pos = i - oldi;
3458                       memcpy(parse, &buf[oldi], parse_pos);
3459                       parse[parse_pos] = NULLCHAR;
3460                       started = STARTED_COMMENT;
3461                       savingComment = TRUE;
3462                     } else if(collective != 3) {
3463                       started = STARTED_CHATTER;
3464                       savingComment = FALSE;
3465                     }
3466                     loggedOn = TRUE;
3467                     continue;
3468                   }
3469                 }
3470
3471                 if (looking_at(buf, &i, "* s-shouts: ") ||
3472                     looking_at(buf, &i, "* c-shouts: ")) {
3473                     if (appData.colorize) {
3474                         if (oldi > next_out) {
3475                             SendToPlayer(&buf[next_out], oldi - next_out);
3476                             next_out = oldi;
3477                         }
3478                         Colorize(ColorSShout, FALSE);
3479                         curColor = ColorSShout;
3480                     }
3481                     loggedOn = TRUE;
3482                     started = STARTED_CHATTER;
3483                     continue;
3484                 }
3485
3486                 if (looking_at(buf, &i, "--->")) {
3487                     loggedOn = TRUE;
3488                     continue;
3489                 }
3490
3491                 if (looking_at(buf, &i, "* shouts: ") ||
3492                     looking_at(buf, &i, "--> ")) {
3493                     if (appData.colorize) {
3494                         if (oldi > next_out) {
3495                             SendToPlayer(&buf[next_out], oldi - next_out);
3496                             next_out = oldi;
3497                         }
3498                         Colorize(ColorShout, FALSE);
3499                         curColor = ColorShout;
3500                     }
3501                     loggedOn = TRUE;
3502                     started = STARTED_CHATTER;
3503                     continue;
3504                 }
3505
3506                 if (looking_at( buf, &i, "Challenge:")) {
3507                     if (appData.colorize) {
3508                         if (oldi > next_out) {
3509                             SendToPlayer(&buf[next_out], oldi - next_out);
3510                             next_out = oldi;
3511                         }
3512                         Colorize(ColorChallenge, FALSE);
3513                         curColor = ColorChallenge;
3514                     }
3515                     loggedOn = TRUE;
3516                     continue;
3517                 }
3518
3519                 if (looking_at(buf, &i, "* offers you") ||
3520                     looking_at(buf, &i, "* offers to be") ||
3521                     looking_at(buf, &i, "* would like to") ||
3522                     looking_at(buf, &i, "* requests to") ||
3523                     looking_at(buf, &i, "Your opponent offers") ||
3524                     looking_at(buf, &i, "Your opponent requests")) {
3525
3526                     if (appData.colorize) {
3527                         if (oldi > next_out) {
3528                             SendToPlayer(&buf[next_out], oldi - next_out);
3529                             next_out = oldi;
3530                         }
3531                         Colorize(ColorRequest, FALSE);
3532                         curColor = ColorRequest;
3533                     }
3534                     continue;
3535                 }
3536
3537                 if (looking_at(buf, &i, "* (*) seeking")) {
3538                     if (appData.colorize) {
3539                         if (oldi > next_out) {
3540                             SendToPlayer(&buf[next_out], oldi - next_out);
3541                             next_out = oldi;
3542                         }
3543                         Colorize(ColorSeek, FALSE);
3544                         curColor = ColorSeek;
3545                     }
3546                     continue;
3547             }
3548
3549           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3550
3551             if (looking_at(buf, &i, "\\   ")) {
3552                 if (prevColor != ColorNormal) {
3553                     if (oldi > next_out) {
3554                         SendToPlayer(&buf[next_out], oldi - next_out);
3555                         next_out = oldi;
3556                     }
3557                     Colorize(prevColor, TRUE);
3558                     curColor = prevColor;
3559                 }
3560                 if (savingComment) {
3561                     parse_pos = i - oldi;
3562                     memcpy(parse, &buf[oldi], parse_pos);
3563                     parse[parse_pos] = NULLCHAR;
3564                     started = STARTED_COMMENT;
3565                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3566                         chattingPartner = savingComment - 3; // kludge to remember the box
3567                 } else {
3568                     started = STARTED_CHATTER;
3569                 }
3570                 continue;
3571             }
3572
3573             if (looking_at(buf, &i, "Black Strength :") ||
3574                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3575                 looking_at(buf, &i, "<10>") ||
3576                 looking_at(buf, &i, "#@#")) {
3577                 /* Wrong board style */
3578                 loggedOn = TRUE;
3579                 SendToICS(ics_prefix);
3580                 SendToICS("set style 12\n");
3581                 SendToICS(ics_prefix);
3582                 SendToICS("refresh\n");
3583                 continue;
3584             }
3585
3586             if (looking_at(buf, &i, "login:")) {
3587               if (!have_sent_ICS_logon) {
3588                 if(ICSInitScript())
3589                   have_sent_ICS_logon = 1;
3590                 else // no init script was found
3591                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3592               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3593                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3594               }
3595                 continue;
3596             }
3597
3598             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3599                 (looking_at(buf, &i, "\n<12> ") ||
3600                  looking_at(buf, &i, "<12> "))) {
3601                 loggedOn = TRUE;
3602                 if (oldi > next_out) {
3603                     SendToPlayer(&buf[next_out], oldi - next_out);
3604                 }
3605                 next_out = i;
3606                 started = STARTED_BOARD;
3607                 parse_pos = 0;
3608                 continue;
3609             }
3610
3611             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3612                 looking_at(buf, &i, "<b1> ")) {
3613                 if (oldi > next_out) {
3614                     SendToPlayer(&buf[next_out], oldi - next_out);
3615                 }
3616                 next_out = i;
3617                 started = STARTED_HOLDINGS;
3618                 parse_pos = 0;
3619                 continue;
3620             }
3621
3622             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3623                 loggedOn = TRUE;
3624                 /* Header for a move list -- first line */
3625
3626                 switch (ics_getting_history) {
3627                   case H_FALSE:
3628                     switch (gameMode) {
3629                       case IcsIdle:
3630                       case BeginningOfGame:
3631                         /* User typed "moves" or "oldmoves" while we
3632                            were idle.  Pretend we asked for these
3633                            moves and soak them up so user can step
3634                            through them and/or save them.
3635                            */
3636                         Reset(FALSE, TRUE);
3637                         gameMode = IcsObserving;
3638                         ModeHighlight();
3639                         ics_gamenum = -1;
3640                         ics_getting_history = H_GOT_UNREQ_HEADER;
3641                         break;
3642                       case EditGame: /*?*/
3643                       case EditPosition: /*?*/
3644                         /* Should above feature work in these modes too? */
3645                         /* For now it doesn't */
3646                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3647                         break;
3648                       default:
3649                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3650                         break;
3651                     }
3652                     break;
3653                   case H_REQUESTED:
3654                     /* Is this the right one? */
3655                     if (gameInfo.white && gameInfo.black &&
3656                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3657                         strcmp(gameInfo.black, star_match[2]) == 0) {
3658                         /* All is well */
3659                         ics_getting_history = H_GOT_REQ_HEADER;
3660                     }
3661                     break;
3662                   case H_GOT_REQ_HEADER:
3663                   case H_GOT_UNREQ_HEADER:
3664                   case H_GOT_UNWANTED_HEADER:
3665                   case H_GETTING_MOVES:
3666                     /* Should not happen */
3667                     DisplayError(_("Error gathering move list: two headers"), 0);
3668                     ics_getting_history = H_FALSE;
3669                     break;
3670                 }
3671
3672                 /* Save player ratings into gameInfo if needed */
3673                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3674                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3675                     (gameInfo.whiteRating == -1 ||
3676                      gameInfo.blackRating == -1)) {
3677
3678                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3679                     gameInfo.blackRating = string_to_rating(star_match[3]);
3680                     if (appData.debugMode)
3681                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3682                               gameInfo.whiteRating, gameInfo.blackRating);
3683                 }
3684                 continue;
3685             }
3686
3687             if (looking_at(buf, &i,
3688               "* * match, initial time: * minute*, increment: * second")) {
3689                 /* Header for a move list -- second line */
3690                 /* Initial board will follow if this is a wild game */
3691                 if (gameInfo.event != NULL) free(gameInfo.event);
3692                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3693                 gameInfo.event = StrSave(str);
3694                 /* [HGM] we switched variant. Translate boards if needed. */
3695                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3696                 continue;
3697             }
3698
3699             if (looking_at(buf, &i, "Move  ")) {
3700                 /* Beginning of a move list */
3701                 switch (ics_getting_history) {
3702                   case H_FALSE:
3703                     /* Normally should not happen */
3704                     /* Maybe user hit reset while we were parsing */
3705                     break;
3706                   case H_REQUESTED:
3707                     /* Happens if we are ignoring a move list that is not
3708                      * the one we just requested.  Common if the user
3709                      * tries to observe two games without turning off
3710                      * getMoveList */
3711                     break;
3712                   case H_GETTING_MOVES:
3713                     /* Should not happen */
3714                     DisplayError(_("Error gathering move list: nested"), 0);
3715                     ics_getting_history = H_FALSE;
3716                     break;
3717                   case H_GOT_REQ_HEADER:
3718                     ics_getting_history = H_GETTING_MOVES;
3719                     started = STARTED_MOVES;
3720                     parse_pos = 0;
3721                     if (oldi > next_out) {
3722                         SendToPlayer(&buf[next_out], oldi - next_out);
3723                     }
3724                     break;
3725                   case H_GOT_UNREQ_HEADER:
3726                     ics_getting_history = H_GETTING_MOVES;
3727                     started = STARTED_MOVES_NOHIDE;
3728                     parse_pos = 0;
3729                     break;
3730                   case H_GOT_UNWANTED_HEADER:
3731                     ics_getting_history = H_FALSE;
3732                     break;
3733                 }
3734                 continue;
3735             }
3736
3737             if (looking_at(buf, &i, "% ") ||
3738                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3739                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3740                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3741                     soughtPending = FALSE;
3742                     seekGraphUp = TRUE;
3743                     DrawSeekGraph();
3744                 }
3745                 if(suppressKibitz) next_out = i;
3746                 savingComment = FALSE;
3747                 suppressKibitz = 0;
3748                 switch (started) {
3749                   case STARTED_MOVES:
3750                   case STARTED_MOVES_NOHIDE:
3751                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3752                     parse[parse_pos + i - oldi] = NULLCHAR;
3753                     ParseGameHistory(parse);
3754 #if ZIPPY
3755                     if (appData.zippyPlay && first.initDone) {
3756                         FeedMovesToProgram(&first, forwardMostMove);
3757                         if (gameMode == IcsPlayingWhite) {
3758                             if (WhiteOnMove(forwardMostMove)) {
3759                                 if (first.sendTime) {
3760                                   if (first.useColors) {
3761                                     SendToProgram("black\n", &first);
3762                                   }
3763                                   SendTimeRemaining(&first, TRUE);
3764                                 }
3765                                 if (first.useColors) {
3766                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3767                                 }
3768                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3769                                 first.maybeThinking = TRUE;
3770                             } else {
3771                                 if (first.usePlayother) {
3772                                   if (first.sendTime) {
3773                                     SendTimeRemaining(&first, TRUE);
3774                                   }
3775                                   SendToProgram("playother\n", &first);
3776                                   firstMove = FALSE;
3777                                 } else {
3778                                   firstMove = TRUE;
3779                                 }
3780                             }
3781                         } else if (gameMode == IcsPlayingBlack) {
3782                             if (!WhiteOnMove(forwardMostMove)) {
3783                                 if (first.sendTime) {
3784                                   if (first.useColors) {
3785                                     SendToProgram("white\n", &first);
3786                                   }
3787                                   SendTimeRemaining(&first, FALSE);
3788                                 }
3789                                 if (first.useColors) {
3790                                   SendToProgram("black\n", &first);
3791                                 }
3792                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3793                                 first.maybeThinking = TRUE;
3794                             } else {
3795                                 if (first.usePlayother) {
3796                                   if (first.sendTime) {
3797                                     SendTimeRemaining(&first, FALSE);
3798                                   }
3799                                   SendToProgram("playother\n", &first);
3800                                   firstMove = FALSE;
3801                                 } else {
3802                                   firstMove = TRUE;
3803                                 }
3804                             }
3805                         }
3806                     }
3807 #endif
3808                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3809                         /* Moves came from oldmoves or moves command
3810                            while we weren't doing anything else.
3811                            */
3812                         currentMove = forwardMostMove;
3813                         ClearHighlights();/*!!could figure this out*/
3814                         flipView = appData.flipView;
3815                         DrawPosition(TRUE, boards[currentMove]);
3816                         DisplayBothClocks();
3817                         snprintf(str, MSG_SIZ, "%s %s %s",
3818                                 gameInfo.white, _("vs."),  gameInfo.black);
3819                         DisplayTitle(str);
3820                         gameMode = IcsIdle;
3821                     } else {
3822                         /* Moves were history of an active game */
3823                         if (gameInfo.resultDetails != NULL) {
3824                             free(gameInfo.resultDetails);
3825                             gameInfo.resultDetails = NULL;
3826                         }
3827                     }
3828                     HistorySet(parseList, backwardMostMove,
3829                                forwardMostMove, currentMove-1);
3830                     DisplayMove(currentMove - 1);
3831                     if (started == STARTED_MOVES) next_out = i;
3832                     started = STARTED_NONE;
3833                     ics_getting_history = H_FALSE;
3834                     break;
3835
3836                   case STARTED_OBSERVE:
3837                     started = STARTED_NONE;
3838                     SendToICS(ics_prefix);
3839                     SendToICS("refresh\n");
3840                     break;
3841
3842                   default:
3843                     break;
3844                 }
3845                 if(bookHit) { // [HGM] book: simulate book reply
3846                     static char bookMove[MSG_SIZ]; // a bit generous?
3847
3848                     programStats.nodes = programStats.depth = programStats.time =
3849                     programStats.score = programStats.got_only_move = 0;
3850                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3851
3852                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3853                     strcat(bookMove, bookHit);
3854                     HandleMachineMove(bookMove, &first);
3855                 }
3856                 continue;
3857             }
3858
3859             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3860                  started == STARTED_HOLDINGS ||
3861                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3862                 /* Accumulate characters in move list or board */
3863                 parse[parse_pos++] = buf[i];
3864             }
3865
3866             /* Start of game messages.  Mostly we detect start of game
3867                when the first board image arrives.  On some versions
3868                of the ICS, though, we need to do a "refresh" after starting
3869                to observe in order to get the current board right away. */
3870             if (looking_at(buf, &i, "Adding game * to observation list")) {
3871                 started = STARTED_OBSERVE;
3872                 continue;
3873             }
3874
3875             /* Handle auto-observe */
3876             if (appData.autoObserve &&
3877                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3878                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3879                 char *player;
3880                 /* Choose the player that was highlighted, if any. */
3881                 if (star_match[0][0] == '\033' ||
3882                     star_match[1][0] != '\033') {
3883                     player = star_match[0];
3884                 } else {
3885                     player = star_match[2];
3886                 }
3887                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3888                         ics_prefix, StripHighlightAndTitle(player));
3889                 SendToICS(str);
3890
3891                 /* Save ratings from notify string */
3892                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3893                 player1Rating = string_to_rating(star_match[1]);
3894                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3895                 player2Rating = string_to_rating(star_match[3]);
3896
3897                 if (appData.debugMode)
3898                   fprintf(debugFP,
3899                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3900                           player1Name, player1Rating,
3901                           player2Name, player2Rating);
3902
3903                 continue;
3904             }
3905
3906             /* Deal with automatic examine mode after a game,
3907                and with IcsObserving -> IcsExamining transition */
3908             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3909                 looking_at(buf, &i, "has made you an examiner of game *")) {
3910
3911                 int gamenum = atoi(star_match[0]);
3912                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3913                     gamenum == ics_gamenum) {
3914                     /* We were already playing or observing this game;
3915                        no need to refetch history */
3916                     gameMode = IcsExamining;
3917                     if (pausing) {
3918                         pauseExamForwardMostMove = forwardMostMove;
3919                     } else if (currentMove < forwardMostMove) {
3920                         ForwardInner(forwardMostMove);
3921                     }
3922                 } else {
3923                     /* I don't think this case really can happen */
3924                     SendToICS(ics_prefix);
3925                     SendToICS("refresh\n");
3926                 }
3927                 continue;
3928             }
3929
3930             /* Error messages */
3931 //          if (ics_user_moved) {
3932             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3933                 if (looking_at(buf, &i, "Illegal move") ||
3934                     looking_at(buf, &i, "Not a legal move") ||
3935                     looking_at(buf, &i, "Your king is in check") ||
3936                     looking_at(buf, &i, "It isn't your turn") ||
3937                     looking_at(buf, &i, "It is not your move")) {
3938                     /* Illegal move */
3939                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3940                         currentMove = forwardMostMove-1;
3941                         DisplayMove(currentMove - 1); /* before DMError */
3942                         DrawPosition(FALSE, boards[currentMove]);
3943                         SwitchClocks(forwardMostMove-1); // [HGM] race
3944                         DisplayBothClocks();
3945                     }
3946                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3947                     ics_user_moved = 0;
3948                     continue;
3949                 }
3950             }
3951
3952             if (looking_at(buf, &i, "still have time") ||
3953                 looking_at(buf, &i, "not out of time") ||
3954                 looking_at(buf, &i, "either player is out of time") ||
3955                 looking_at(buf, &i, "has timeseal; checking")) {
3956                 /* We must have called his flag a little too soon */
3957                 whiteFlag = blackFlag = FALSE;
3958                 continue;
3959             }
3960
3961             if (looking_at(buf, &i, "added * seconds to") ||
3962                 looking_at(buf, &i, "seconds were added to")) {
3963                 /* Update the clocks */
3964                 SendToICS(ics_prefix);
3965                 SendToICS("refresh\n");
3966                 continue;
3967             }
3968
3969             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3970                 ics_clock_paused = TRUE;
3971                 StopClocks();
3972                 continue;
3973             }
3974
3975             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3976                 ics_clock_paused = FALSE;
3977                 StartClocks();
3978                 continue;
3979             }
3980
3981             /* Grab player ratings from the Creating: message.
3982                Note we have to check for the special case when
3983                the ICS inserts things like [white] or [black]. */
3984             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3985                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3986                 /* star_matches:
3987                    0    player 1 name (not necessarily white)
3988                    1    player 1 rating
3989                    2    empty, white, or black (IGNORED)
3990                    3    player 2 name (not necessarily black)
3991                    4    player 2 rating
3992
3993                    The names/ratings are sorted out when the game
3994                    actually starts (below).
3995                 */
3996                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3997                 player1Rating = string_to_rating(star_match[1]);
3998                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3999                 player2Rating = string_to_rating(star_match[4]);
4000
4001                 if (appData.debugMode)
4002                   fprintf(debugFP,
4003                           "Ratings from 'Creating:' %s %d, %s %d\n",
4004                           player1Name, player1Rating,
4005                           player2Name, player2Rating);
4006
4007                 continue;
4008             }
4009
4010             /* Improved generic start/end-of-game messages */
4011             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4012                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4013                 /* If tkind == 0: */
4014                 /* star_match[0] is the game number */
4015                 /*           [1] is the white player's name */
4016                 /*           [2] is the black player's name */
4017                 /* For end-of-game: */
4018                 /*           [3] is the reason for the game end */
4019                 /*           [4] is a PGN end game-token, preceded by " " */
4020                 /* For start-of-game: */
4021                 /*           [3] begins with "Creating" or "Continuing" */
4022                 /*           [4] is " *" or empty (don't care). */
4023                 int gamenum = atoi(star_match[0]);
4024                 char *whitename, *blackname, *why, *endtoken;
4025                 ChessMove endtype = EndOfFile;
4026
4027                 if (tkind == 0) {
4028                   whitename = star_match[1];
4029                   blackname = star_match[2];
4030                   why = star_match[3];
4031                   endtoken = star_match[4];
4032                 } else {
4033                   whitename = star_match[1];
4034                   blackname = star_match[3];
4035                   why = star_match[5];
4036                   endtoken = star_match[6];
4037                 }
4038
4039                 /* Game start messages */
4040                 if (strncmp(why, "Creating ", 9) == 0 ||
4041                     strncmp(why, "Continuing ", 11) == 0) {
4042                     gs_gamenum = gamenum;
4043                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4044                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4045                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4046 #if ZIPPY
4047                     if (appData.zippyPlay) {
4048                         ZippyGameStart(whitename, blackname);
4049                     }
4050 #endif /*ZIPPY*/
4051                     partnerBoardValid = FALSE; // [HGM] bughouse
4052                     continue;
4053                 }
4054
4055                 /* Game end messages */
4056                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4057                     ics_gamenum != gamenum) {
4058                     continue;
4059                 }
4060                 while (endtoken[0] == ' ') endtoken++;
4061                 switch (endtoken[0]) {
4062                   case '*':
4063                   default:
4064                     endtype = GameUnfinished;
4065                     break;
4066                   case '0':
4067                     endtype = BlackWins;
4068                     break;
4069                   case '1':
4070                     if (endtoken[1] == '/')
4071                       endtype = GameIsDrawn;
4072                     else
4073                       endtype = WhiteWins;
4074                     break;
4075                 }
4076                 GameEnds(endtype, why, GE_ICS);
4077 #if ZIPPY
4078                 if (appData.zippyPlay && first.initDone) {
4079                     ZippyGameEnd(endtype, why);
4080                     if (first.pr == NoProc) {
4081                       /* Start the next process early so that we'll
4082                          be ready for the next challenge */
4083                       StartChessProgram(&first);
4084                     }
4085                     /* Send "new" early, in case this command takes
4086                        a long time to finish, so that we'll be ready
4087                        for the next challenge. */
4088                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4089                     Reset(TRUE, TRUE);
4090                 }
4091 #endif /*ZIPPY*/
4092                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4093                 continue;
4094             }
4095
4096             if (looking_at(buf, &i, "Removing game * from observation") ||
4097                 looking_at(buf, &i, "no longer observing game *") ||
4098                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4099                 if (gameMode == IcsObserving &&
4100                     atoi(star_match[0]) == ics_gamenum)
4101                   {
4102                       /* icsEngineAnalyze */
4103                       if (appData.icsEngineAnalyze) {
4104                             ExitAnalyzeMode();
4105                             ModeHighlight();
4106                       }
4107                       StopClocks();
4108                       gameMode = IcsIdle;
4109                       ics_gamenum = -1;
4110                       ics_user_moved = FALSE;
4111                   }
4112                 continue;
4113             }
4114
4115             if (looking_at(buf, &i, "no longer examining game *")) {
4116                 if (gameMode == IcsExamining &&
4117                     atoi(star_match[0]) == ics_gamenum)
4118                   {
4119                       gameMode = IcsIdle;
4120                       ics_gamenum = -1;
4121                       ics_user_moved = FALSE;
4122                   }
4123                 continue;
4124             }
4125
4126             /* Advance leftover_start past any newlines we find,
4127                so only partial lines can get reparsed */
4128             if (looking_at(buf, &i, "\n")) {
4129                 prevColor = curColor;
4130                 if (curColor != ColorNormal) {
4131                     if (oldi > next_out) {
4132                         SendToPlayer(&buf[next_out], oldi - next_out);
4133                         next_out = oldi;
4134                     }
4135                     Colorize(ColorNormal, FALSE);
4136                     curColor = ColorNormal;
4137                 }
4138                 if (started == STARTED_BOARD) {
4139                     started = STARTED_NONE;
4140                     parse[parse_pos] = NULLCHAR;
4141                     ParseBoard12(parse);
4142                     ics_user_moved = 0;
4143
4144                     /* Send premove here */
4145                     if (appData.premove) {
4146                       char str[MSG_SIZ];
4147                       if (currentMove == 0 &&
4148                           gameMode == IcsPlayingWhite &&
4149                           appData.premoveWhite) {
4150                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4151                         if (appData.debugMode)
4152                           fprintf(debugFP, "Sending premove:\n");
4153                         SendToICS(str);
4154                       } else if (currentMove == 1 &&
4155                                  gameMode == IcsPlayingBlack &&
4156                                  appData.premoveBlack) {
4157                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4158                         if (appData.debugMode)
4159                           fprintf(debugFP, "Sending premove:\n");
4160                         SendToICS(str);
4161                       } else if (gotPremove) {
4162                         gotPremove = 0;
4163                         ClearPremoveHighlights();
4164                         if (appData.debugMode)
4165                           fprintf(debugFP, "Sending premove:\n");
4166                           UserMoveEvent(premoveFromX, premoveFromY,
4167                                         premoveToX, premoveToY,
4168                                         premovePromoChar);
4169                       }
4170                     }
4171
4172                     /* Usually suppress following prompt */
4173                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4174                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4175                         if (looking_at(buf, &i, "*% ")) {
4176                             savingComment = FALSE;
4177                             suppressKibitz = 0;
4178                         }
4179                     }
4180                     next_out = i;
4181                 } else if (started == STARTED_HOLDINGS) {
4182                     int gamenum;
4183                     char new_piece[MSG_SIZ];
4184                     started = STARTED_NONE;
4185                     parse[parse_pos] = NULLCHAR;
4186                     if (appData.debugMode)
4187                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4188                                                         parse, currentMove);
4189                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4190                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4191                         if (gameInfo.variant == VariantNormal) {
4192                           /* [HGM] We seem to switch variant during a game!
4193                            * Presumably no holdings were displayed, so we have
4194                            * to move the position two files to the right to
4195                            * create room for them!
4196                            */
4197                           VariantClass newVariant;
4198                           switch(gameInfo.boardWidth) { // base guess on board width
4199                                 case 9:  newVariant = VariantShogi; break;
4200                                 case 10: newVariant = VariantGreat; break;
4201                                 default: newVariant = VariantCrazyhouse; break;
4202                           }
4203                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4204                           /* Get a move list just to see the header, which
4205                              will tell us whether this is really bug or zh */
4206                           if (ics_getting_history == H_FALSE) {
4207                             ics_getting_history = H_REQUESTED;
4208                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4209                             SendToICS(str);
4210                           }
4211                         }
4212                         new_piece[0] = NULLCHAR;
4213                         sscanf(parse, "game %d white [%s black [%s <- %s",
4214                                &gamenum, white_holding, black_holding,
4215                                new_piece);
4216                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4217                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4218                         /* [HGM] copy holdings to board holdings area */
4219                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4220                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4221                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4222 #if ZIPPY
4223                         if (appData.zippyPlay && first.initDone) {
4224                             ZippyHoldings(white_holding, black_holding,
4225                                           new_piece);
4226                         }
4227 #endif /*ZIPPY*/
4228                         if (tinyLayout || smallLayout) {
4229                             char wh[16], bh[16];
4230                             PackHolding(wh, white_holding);
4231                             PackHolding(bh, black_holding);
4232                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4233                                     gameInfo.white, gameInfo.black);
4234                         } else {
4235                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4236                                     gameInfo.white, white_holding, _("vs."),
4237                                     gameInfo.black, black_holding);
4238                         }
4239                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4240                         DrawPosition(FALSE, boards[currentMove]);
4241                         DisplayTitle(str);
4242                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4243                         sscanf(parse, "game %d white [%s black [%s <- %s",
4244                                &gamenum, white_holding, black_holding,
4245                                new_piece);
4246                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4247                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4248                         /* [HGM] copy holdings to partner-board holdings area */
4249                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4250                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4251                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4252                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4253                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4254                       }
4255                     }
4256                     /* Suppress following prompt */
4257                     if (looking_at(buf, &i, "*% ")) {
4258                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4259                         savingComment = FALSE;
4260                         suppressKibitz = 0;
4261                     }
4262                     next_out = i;
4263                 }
4264                 continue;
4265             }
4266
4267             i++;                /* skip unparsed character and loop back */
4268         }
4269
4270         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4271 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4272 //          SendToPlayer(&buf[next_out], i - next_out);
4273             started != STARTED_HOLDINGS && leftover_start > next_out) {
4274             SendToPlayer(&buf[next_out], leftover_start - next_out);
4275             next_out = i;
4276         }
4277
4278         leftover_len = buf_len - leftover_start;
4279         /* if buffer ends with something we couldn't parse,
4280            reparse it after appending the next read */
4281
4282     } else if (count == 0) {
4283         RemoveInputSource(isr);
4284         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4285     } else {
4286         DisplayFatalError(_("Error reading from ICS"), error, 1);
4287     }
4288 }
4289
4290
4291 /* Board style 12 looks like this:
4292
4293    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4294
4295  * The "<12> " is stripped before it gets to this routine.  The two
4296  * trailing 0's (flip state and clock ticking) are later addition, and
4297  * some chess servers may not have them, or may have only the first.
4298  * Additional trailing fields may be added in the future.
4299  */
4300
4301 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4302
4303 #define RELATION_OBSERVING_PLAYED    0
4304 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4305 #define RELATION_PLAYING_MYMOVE      1
4306 #define RELATION_PLAYING_NOTMYMOVE  -1
4307 #define RELATION_EXAMINING           2
4308 #define RELATION_ISOLATED_BOARD     -3
4309 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4310
4311 void
4312 ParseBoard12 (char *string)
4313 {
4314 #if ZIPPY
4315     int i, takeback;
4316     char *bookHit = NULL; // [HGM] book
4317 #endif
4318     GameMode newGameMode;
4319     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4320     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4321     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4322     char to_play, board_chars[200];
4323     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4324     char black[32], white[32];
4325     Board board;
4326     int prevMove = currentMove;
4327     int ticking = 2;
4328     ChessMove moveType;
4329     int fromX, fromY, toX, toY;
4330     char promoChar;
4331     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4332     Boolean weird = FALSE, reqFlag = FALSE;
4333
4334     fromX = fromY = toX = toY = -1;
4335
4336     newGame = FALSE;
4337
4338     if (appData.debugMode)
4339       fprintf(debugFP, "Parsing board: %s\n", string);
4340
4341     move_str[0] = NULLCHAR;
4342     elapsed_time[0] = NULLCHAR;
4343     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4344         int  i = 0, j;
4345         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4346             if(string[i] == ' ') { ranks++; files = 0; }
4347             else files++;
4348             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4349             i++;
4350         }
4351         for(j = 0; j <i; j++) board_chars[j] = string[j];
4352         board_chars[i] = '\0';
4353         string += i + 1;
4354     }
4355     n = sscanf(string, PATTERN, &to_play, &double_push,
4356                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4357                &gamenum, white, black, &relation, &basetime, &increment,
4358                &white_stren, &black_stren, &white_time, &black_time,
4359                &moveNum, str, elapsed_time, move_str, &ics_flip,
4360                &ticking);
4361
4362     if (n < 21) {
4363         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4364         DisplayError(str, 0);
4365         return;
4366     }
4367
4368     /* Convert the move number to internal form */
4369     moveNum = (moveNum - 1) * 2;
4370     if (to_play == 'B') moveNum++;
4371     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4372       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4373                         0, 1);
4374       return;
4375     }
4376
4377     switch (relation) {
4378       case RELATION_OBSERVING_PLAYED:
4379       case RELATION_OBSERVING_STATIC:
4380         if (gamenum == -1) {
4381             /* Old ICC buglet */
4382             relation = RELATION_OBSERVING_STATIC;
4383         }
4384         newGameMode = IcsObserving;
4385         break;
4386       case RELATION_PLAYING_MYMOVE:
4387       case RELATION_PLAYING_NOTMYMOVE:
4388         newGameMode =
4389           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4390             IcsPlayingWhite : IcsPlayingBlack;
4391         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4392         break;
4393       case RELATION_EXAMINING:
4394         newGameMode = IcsExamining;
4395         break;
4396       case RELATION_ISOLATED_BOARD:
4397       default:
4398         /* Just display this board.  If user was doing something else,
4399            we will forget about it until the next board comes. */
4400         newGameMode = IcsIdle;
4401         break;
4402       case RELATION_STARTING_POSITION:
4403         newGameMode = gameMode;
4404         break;
4405     }
4406
4407     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4408         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4409          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4410       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4411       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4412       static int lastBgGame = -1;
4413       char *toSqr;
4414       for (k = 0; k < ranks; k++) {
4415         for (j = 0; j < files; j++)
4416           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4417         if(gameInfo.holdingsWidth > 1) {
4418              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4419              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4420         }
4421       }
4422       CopyBoard(partnerBoard, board);
4423       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4424         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4425         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4426       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4427       if(toSqr = strchr(str, '-')) {
4428         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4429         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4430       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4431       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4432       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4433       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4434       if(twoBoards) {
4435           DisplayWhiteClock(white_time*fac, to_play == 'W');
4436           DisplayBlackClock(black_time*fac, to_play != 'W');
4437           activePartner = to_play;
4438           if(gamenum != lastBgGame) {
4439               char buf[MSG_SIZ];
4440               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4441               DisplayTitle(buf);
4442           }
4443           lastBgGame = gamenum;
4444           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4445                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4446       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4447                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4448       if(!twoBoards) DisplayMessage(partnerStatus, "");
4449         partnerBoardValid = TRUE;
4450       return;
4451     }
4452
4453     if(appData.dualBoard && appData.bgObserve) {
4454         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4455             SendToICS(ics_prefix), SendToICS("pobserve\n");
4456         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4457             char buf[MSG_SIZ];
4458             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4459             SendToICS(buf);
4460         }
4461     }
4462
4463     /* Modify behavior for initial board display on move listing
4464        of wild games.
4465        */
4466     switch (ics_getting_history) {
4467       case H_FALSE:
4468       case H_REQUESTED:
4469         break;
4470       case H_GOT_REQ_HEADER:
4471       case H_GOT_UNREQ_HEADER:
4472         /* This is the initial position of the current game */
4473         gamenum = ics_gamenum;
4474         moveNum = 0;            /* old ICS bug workaround */
4475         if (to_play == 'B') {
4476           startedFromSetupPosition = TRUE;
4477           blackPlaysFirst = TRUE;
4478           moveNum = 1;
4479           if (forwardMostMove == 0) forwardMostMove = 1;
4480           if (backwardMostMove == 0) backwardMostMove = 1;
4481           if (currentMove == 0) currentMove = 1;
4482         }
4483         newGameMode = gameMode;
4484         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4485         break;
4486       case H_GOT_UNWANTED_HEADER:
4487         /* This is an initial board that we don't want */
4488         return;
4489       case H_GETTING_MOVES:
4490         /* Should not happen */
4491         DisplayError(_("Error gathering move list: extra board"), 0);
4492         ics_getting_history = H_FALSE;
4493         return;
4494     }
4495
4496    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4497                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4498                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4499      /* [HGM] We seem to have switched variant unexpectedly
4500       * Try to guess new variant from board size
4501       */
4502           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4503           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4504           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4505           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4506           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4507           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4508           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4509           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4510           /* Get a move list just to see the header, which
4511              will tell us whether this is really bug or zh */
4512           if (ics_getting_history == H_FALSE) {
4513             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4514             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4515             SendToICS(str);
4516           }
4517     }
4518
4519     /* Take action if this is the first board of a new game, or of a
4520        different game than is currently being displayed.  */
4521     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4522         relation == RELATION_ISOLATED_BOARD) {
4523
4524         /* Forget the old game and get the history (if any) of the new one */
4525         if (gameMode != BeginningOfGame) {
4526           Reset(TRUE, TRUE);
4527         }
4528         newGame = TRUE;
4529         if (appData.autoRaiseBoard) BoardToTop();
4530         prevMove = -3;
4531         if (gamenum == -1) {
4532             newGameMode = IcsIdle;
4533         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4534                    appData.getMoveList && !reqFlag) {
4535             /* Need to get game history */
4536             ics_getting_history = H_REQUESTED;
4537             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4538             SendToICS(str);
4539         }
4540
4541         /* Initially flip the board to have black on the bottom if playing
4542            black or if the ICS flip flag is set, but let the user change
4543            it with the Flip View button. */
4544         flipView = appData.autoFlipView ?
4545           (newGameMode == IcsPlayingBlack) || ics_flip :
4546           appData.flipView;
4547
4548         /* Done with values from previous mode; copy in new ones */
4549         gameMode = newGameMode;
4550         ModeHighlight();
4551         ics_gamenum = gamenum;
4552         if (gamenum == gs_gamenum) {
4553             int klen = strlen(gs_kind);
4554             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4555             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4556             gameInfo.event = StrSave(str);
4557         } else {
4558             gameInfo.event = StrSave("ICS game");
4559         }
4560         gameInfo.site = StrSave(appData.icsHost);
4561         gameInfo.date = PGNDate();
4562         gameInfo.round = StrSave("-");
4563         gameInfo.white = StrSave(white);
4564         gameInfo.black = StrSave(black);
4565         timeControl = basetime * 60 * 1000;
4566         timeControl_2 = 0;
4567         timeIncrement = increment * 1000;
4568         movesPerSession = 0;
4569         gameInfo.timeControl = TimeControlTagValue();
4570         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4571   if (appData.debugMode) {
4572     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4573     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4574     setbuf(debugFP, NULL);
4575   }
4576
4577         gameInfo.outOfBook = NULL;
4578
4579         /* Do we have the ratings? */
4580         if (strcmp(player1Name, white) == 0 &&
4581             strcmp(player2Name, black) == 0) {
4582             if (appData.debugMode)
4583               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4584                       player1Rating, player2Rating);
4585             gameInfo.whiteRating = player1Rating;
4586             gameInfo.blackRating = player2Rating;
4587         } else if (strcmp(player2Name, white) == 0 &&
4588                    strcmp(player1Name, black) == 0) {
4589             if (appData.debugMode)
4590               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4591                       player2Rating, player1Rating);
4592             gameInfo.whiteRating = player2Rating;
4593             gameInfo.blackRating = player1Rating;
4594         }
4595         player1Name[0] = player2Name[0] = NULLCHAR;
4596
4597         /* Silence shouts if requested */
4598         if (appData.quietPlay &&
4599             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4600             SendToICS(ics_prefix);
4601             SendToICS("set shout 0\n");
4602         }
4603     }
4604
4605     /* Deal with midgame name changes */
4606     if (!newGame) {
4607         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4608             if (gameInfo.white) free(gameInfo.white);
4609             gameInfo.white = StrSave(white);
4610         }
4611         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4612             if (gameInfo.black) free(gameInfo.black);
4613             gameInfo.black = StrSave(black);
4614         }
4615     }
4616
4617     /* Throw away game result if anything actually changes in examine mode */
4618     if (gameMode == IcsExamining && !newGame) {
4619         gameInfo.result = GameUnfinished;
4620         if (gameInfo.resultDetails != NULL) {
4621             free(gameInfo.resultDetails);
4622             gameInfo.resultDetails = NULL;
4623         }
4624     }
4625
4626     /* In pausing && IcsExamining mode, we ignore boards coming
4627        in if they are in a different variation than we are. */
4628     if (pauseExamInvalid) return;
4629     if (pausing && gameMode == IcsExamining) {
4630         if (moveNum <= pauseExamForwardMostMove) {
4631             pauseExamInvalid = TRUE;
4632             forwardMostMove = pauseExamForwardMostMove;
4633             return;
4634         }
4635     }
4636
4637   if (appData.debugMode) {
4638     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4639   }
4640     /* Parse the board */
4641     for (k = 0; k < ranks; k++) {
4642       for (j = 0; j < files; j++)
4643         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4644       if(gameInfo.holdingsWidth > 1) {
4645            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4646            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4647       }
4648     }
4649     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4650       board[5][BOARD_RGHT+1] = WhiteAngel;
4651       board[6][BOARD_RGHT+1] = WhiteMarshall;
4652       board[1][0] = BlackMarshall;
4653       board[2][0] = BlackAngel;
4654       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4655     }
4656     CopyBoard(boards[moveNum], board);
4657     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4658     if (moveNum == 0) {
4659         startedFromSetupPosition =
4660           !CompareBoards(board, initialPosition);
4661         if(startedFromSetupPosition)
4662             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4663     }
4664
4665     /* [HGM] Set castling rights. Take the outermost Rooks,
4666        to make it also work for FRC opening positions. Note that board12
4667        is really defective for later FRC positions, as it has no way to
4668        indicate which Rook can castle if they are on the same side of King.
4669        For the initial position we grant rights to the outermost Rooks,
4670        and remember thos rights, and we then copy them on positions
4671        later in an FRC game. This means WB might not recognize castlings with
4672        Rooks that have moved back to their original position as illegal,
4673        but in ICS mode that is not its job anyway.
4674     */
4675     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4676     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4677
4678         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4679             if(board[0][i] == WhiteRook) j = i;
4680         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4681         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4682             if(board[0][i] == WhiteRook) j = i;
4683         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4684         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4685             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4686         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4687         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4688             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4689         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4690
4691         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4692         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4693         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4694             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4695         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4696             if(board[BOARD_HEIGHT-1][k] == bKing)
4697                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4698         if(gameInfo.variant == VariantTwoKings) {
4699             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4700             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4701             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4702         }
4703     } else { int r;
4704         r = boards[moveNum][CASTLING][0] = initialRights[0];
4705         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4706         r = boards[moveNum][CASTLING][1] = initialRights[1];
4707         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4708         r = boards[moveNum][CASTLING][3] = initialRights[3];
4709         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4710         r = boards[moveNum][CASTLING][4] = initialRights[4];
4711         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4712         /* wildcastle kludge: always assume King has rights */
4713         r = boards[moveNum][CASTLING][2] = initialRights[2];
4714         r = boards[moveNum][CASTLING][5] = initialRights[5];
4715     }
4716     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4717     boards[moveNum][EP_STATUS] = EP_NONE;
4718     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4719     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4720     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4721
4722
4723     if (ics_getting_history == H_GOT_REQ_HEADER ||
4724         ics_getting_history == H_GOT_UNREQ_HEADER) {
4725         /* This was an initial position from a move list, not
4726            the current position */
4727         return;
4728     }
4729
4730     /* Update currentMove and known move number limits */
4731     newMove = newGame || moveNum > forwardMostMove;
4732
4733     if (newGame) {
4734         forwardMostMove = backwardMostMove = currentMove = moveNum;
4735         if (gameMode == IcsExamining && moveNum == 0) {
4736           /* Workaround for ICS limitation: we are not told the wild
4737              type when starting to examine a game.  But if we ask for
4738              the move list, the move list header will tell us */
4739             ics_getting_history = H_REQUESTED;
4740             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4741             SendToICS(str);
4742         }
4743     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4744                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4745 #if ZIPPY
4746         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4747         /* [HGM] applied this also to an engine that is silently watching        */
4748         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4749             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4750             gameInfo.variant == currentlyInitializedVariant) {
4751           takeback = forwardMostMove - moveNum;
4752           for (i = 0; i < takeback; i++) {
4753             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4754             SendToProgram("undo\n", &first);
4755           }
4756         }
4757 #endif
4758
4759         forwardMostMove = moveNum;
4760         if (!pausing || currentMove > forwardMostMove)
4761           currentMove = forwardMostMove;
4762     } else {
4763         /* New part of history that is not contiguous with old part */
4764         if (pausing && gameMode == IcsExamining) {
4765             pauseExamInvalid = TRUE;
4766             forwardMostMove = pauseExamForwardMostMove;
4767             return;
4768         }
4769         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4770 #if ZIPPY
4771             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4772                 // [HGM] when we will receive the move list we now request, it will be
4773                 // fed to the engine from the first move on. So if the engine is not
4774                 // in the initial position now, bring it there.
4775                 InitChessProgram(&first, 0);
4776             }
4777 #endif
4778             ics_getting_history = H_REQUESTED;
4779             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4780             SendToICS(str);
4781         }
4782         forwardMostMove = backwardMostMove = currentMove = moveNum;
4783     }
4784
4785     /* Update the clocks */
4786     if (strchr(elapsed_time, '.')) {
4787       /* Time is in ms */
4788       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4789       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4790     } else {
4791       /* Time is in seconds */
4792       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4793       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4794     }
4795
4796
4797 #if ZIPPY
4798     if (appData.zippyPlay && newGame &&
4799         gameMode != IcsObserving && gameMode != IcsIdle &&
4800         gameMode != IcsExamining)
4801       ZippyFirstBoard(moveNum, basetime, increment);
4802 #endif
4803
4804     /* Put the move on the move list, first converting
4805        to canonical algebraic form. */
4806     if (moveNum > 0) {
4807   if (appData.debugMode) {
4808     int f = forwardMostMove;
4809     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4810             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4811             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4812     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4813     fprintf(debugFP, "moveNum = %d\n", moveNum);
4814     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4815     setbuf(debugFP, NULL);
4816   }
4817         if (moveNum <= backwardMostMove) {
4818             /* We don't know what the board looked like before
4819                this move.  Punt. */
4820           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4821             strcat(parseList[moveNum - 1], " ");
4822             strcat(parseList[moveNum - 1], elapsed_time);
4823             moveList[moveNum - 1][0] = NULLCHAR;
4824         } else if (strcmp(move_str, "none") == 0) {
4825             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4826             /* Again, we don't know what the board looked like;
4827                this is really the start of the game. */
4828             parseList[moveNum - 1][0] = NULLCHAR;
4829             moveList[moveNum - 1][0] = NULLCHAR;
4830             backwardMostMove = moveNum;
4831             startedFromSetupPosition = TRUE;
4832             fromX = fromY = toX = toY = -1;
4833         } else {
4834           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4835           //                 So we parse the long-algebraic move string in stead of the SAN move
4836           int valid; char buf[MSG_SIZ], *prom;
4837
4838           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4839                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4840           // str looks something like "Q/a1-a2"; kill the slash
4841           if(str[1] == '/')
4842             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4843           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4844           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4845                 strcat(buf, prom); // long move lacks promo specification!
4846           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4847                 if(appData.debugMode)
4848                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4849                 safeStrCpy(move_str, buf, MSG_SIZ);
4850           }
4851           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4852                                 &fromX, &fromY, &toX, &toY, &promoChar)
4853                || ParseOneMove(buf, moveNum - 1, &moveType,
4854                                 &fromX, &fromY, &toX, &toY, &promoChar);
4855           // end of long SAN patch
4856           if (valid) {
4857             (void) CoordsToAlgebraic(boards[moveNum - 1],
4858                                      PosFlags(moveNum - 1),
4859                                      fromY, fromX, toY, toX, promoChar,
4860                                      parseList[moveNum-1]);
4861             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4862               case MT_NONE:
4863               case MT_STALEMATE:
4864               default:
4865                 break;
4866               case MT_CHECK:
4867                 if(!IS_SHOGI(gameInfo.variant))
4868                     strcat(parseList[moveNum - 1], "+");
4869                 break;
4870               case MT_CHECKMATE:
4871               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4872                 strcat(parseList[moveNum - 1], "#");
4873                 break;
4874             }
4875             strcat(parseList[moveNum - 1], " ");
4876             strcat(parseList[moveNum - 1], elapsed_time);
4877             /* currentMoveString is set as a side-effect of ParseOneMove */
4878             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4879             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4880             strcat(moveList[moveNum - 1], "\n");
4881
4882             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4883                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4884               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4885                 ChessSquare old, new = boards[moveNum][k][j];
4886                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4887                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4888                   if(old == new) continue;
4889                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4890                   else if(new == WhiteWazir || new == BlackWazir) {
4891                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4892                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4893                       else boards[moveNum][k][j] = old; // preserve type of Gold
4894                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4895                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4896               }
4897           } else {
4898             /* Move from ICS was illegal!?  Punt. */
4899             if (appData.debugMode) {
4900               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4901               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4902             }
4903             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4904             strcat(parseList[moveNum - 1], " ");
4905             strcat(parseList[moveNum - 1], elapsed_time);
4906             moveList[moveNum - 1][0] = NULLCHAR;
4907             fromX = fromY = toX = toY = -1;
4908           }
4909         }
4910   if (appData.debugMode) {
4911     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4912     setbuf(debugFP, NULL);
4913   }
4914
4915 #if ZIPPY
4916         /* Send move to chess program (BEFORE animating it). */
4917         if (appData.zippyPlay && !newGame && newMove &&
4918            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4919
4920             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4921                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4922                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4923                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4924                             move_str);
4925                     DisplayError(str, 0);
4926                 } else {
4927                     if (first.sendTime) {
4928                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4929                     }
4930                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4931                     if (firstMove && !bookHit) {
4932                         firstMove = FALSE;
4933                         if (first.useColors) {
4934                           SendToProgram(gameMode == IcsPlayingWhite ?
4935                                         "white\ngo\n" :
4936                                         "black\ngo\n", &first);
4937                         } else {
4938                           SendToProgram("go\n", &first);
4939                         }
4940                         first.maybeThinking = TRUE;
4941                     }
4942                 }
4943             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4944               if (moveList[moveNum - 1][0] == NULLCHAR) {
4945                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4946                 DisplayError(str, 0);
4947               } else {
4948                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4949                 SendMoveToProgram(moveNum - 1, &first);
4950               }
4951             }
4952         }
4953 #endif
4954     }
4955
4956     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4957         /* If move comes from a remote source, animate it.  If it
4958            isn't remote, it will have already been animated. */
4959         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4960             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4961         }
4962         if (!pausing && appData.highlightLastMove) {
4963             SetHighlights(fromX, fromY, toX, toY);
4964         }
4965     }
4966
4967     /* Start the clocks */
4968     whiteFlag = blackFlag = FALSE;
4969     appData.clockMode = !(basetime == 0 && increment == 0);
4970     if (ticking == 0) {
4971       ics_clock_paused = TRUE;
4972       StopClocks();
4973     } else if (ticking == 1) {
4974       ics_clock_paused = FALSE;
4975     }
4976     if (gameMode == IcsIdle ||
4977         relation == RELATION_OBSERVING_STATIC ||
4978         relation == RELATION_EXAMINING ||
4979         ics_clock_paused)
4980       DisplayBothClocks();
4981     else
4982       StartClocks();
4983
4984     /* Display opponents and material strengths */
4985     if (gameInfo.variant != VariantBughouse &&
4986         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4987         if (tinyLayout || smallLayout) {
4988             if(gameInfo.variant == VariantNormal)
4989               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4990                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4991                     basetime, increment);
4992             else
4993               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4994                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4995                     basetime, increment, (int) gameInfo.variant);
4996         } else {
4997             if(gameInfo.variant == VariantNormal)
4998               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4999                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5000                     basetime, increment);
5001             else
5002               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5003                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5004                     basetime, increment, VariantName(gameInfo.variant));
5005         }
5006         DisplayTitle(str);
5007   if (appData.debugMode) {
5008     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5009   }
5010     }
5011
5012
5013     /* Display the board */
5014     if (!pausing && !appData.noGUI) {
5015
5016       if (appData.premove)
5017           if (!gotPremove ||
5018              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5019              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5020               ClearPremoveHighlights();
5021
5022       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5023         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5024       DrawPosition(j, boards[currentMove]);
5025
5026       DisplayMove(moveNum - 1);
5027       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5028             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5029               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5030         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5031       }
5032     }
5033
5034     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5035 #if ZIPPY
5036     if(bookHit) { // [HGM] book: simulate book reply
5037         static char bookMove[MSG_SIZ]; // a bit generous?
5038
5039         programStats.nodes = programStats.depth = programStats.time =
5040         programStats.score = programStats.got_only_move = 0;
5041         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5042
5043         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5044         strcat(bookMove, bookHit);
5045         HandleMachineMove(bookMove, &first);
5046     }
5047 #endif
5048 }
5049
5050 void
5051 GetMoveListEvent ()
5052 {
5053     char buf[MSG_SIZ];
5054     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5055         ics_getting_history = H_REQUESTED;
5056         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5057         SendToICS(buf);
5058     }
5059 }
5060
5061 void
5062 SendToBoth (char *msg)
5063 {   // to make it easy to keep two engines in step in dual analysis
5064     SendToProgram(msg, &first);
5065     if(second.analyzing) SendToProgram(msg, &second);
5066 }
5067
5068 void
5069 AnalysisPeriodicEvent (int force)
5070 {
5071     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5072          && !force) || !appData.periodicUpdates)
5073       return;
5074
5075     /* Send . command to Crafty to collect stats */
5076     SendToBoth(".\n");
5077
5078     /* Don't send another until we get a response (this makes
5079        us stop sending to old Crafty's which don't understand
5080        the "." command (sending illegal cmds resets node count & time,
5081        which looks bad)) */
5082     programStats.ok_to_send = 0;
5083 }
5084
5085 void
5086 ics_update_width (int new_width)
5087 {
5088         ics_printf("set width %d\n", new_width);
5089 }
5090
5091 void
5092 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5093 {
5094     char buf[MSG_SIZ];
5095
5096     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5097         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5098             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5099             SendToProgram(buf, cps);
5100             return;
5101         }
5102         // null move in variant where engine does not understand it (for analysis purposes)
5103         SendBoard(cps, moveNum + 1); // send position after move in stead.
5104         return;
5105     }
5106     if (cps->useUsermove) {
5107       SendToProgram("usermove ", cps);
5108     }
5109     if (cps->useSAN) {
5110       char *space;
5111       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5112         int len = space - parseList[moveNum];
5113         memcpy(buf, parseList[moveNum], len);
5114         buf[len++] = '\n';
5115         buf[len] = NULLCHAR;
5116       } else {
5117         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5118       }
5119       SendToProgram(buf, cps);
5120     } else {
5121       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5122         AlphaRank(moveList[moveNum], 4);
5123         SendToProgram(moveList[moveNum], cps);
5124         AlphaRank(moveList[moveNum], 4); // and back
5125       } else
5126       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5127        * the engine. It would be nice to have a better way to identify castle
5128        * moves here. */
5129       if(appData.fischerCastling && cps->useOOCastle) {
5130         int fromX = moveList[moveNum][0] - AAA;
5131         int fromY = moveList[moveNum][1] - ONE;
5132         int toX = moveList[moveNum][2] - AAA;
5133         int toY = moveList[moveNum][3] - ONE;
5134         if((boards[moveNum][fromY][fromX] == WhiteKing
5135             && boards[moveNum][toY][toX] == WhiteRook)
5136            || (boards[moveNum][fromY][fromX] == BlackKing
5137                && boards[moveNum][toY][toX] == BlackRook)) {
5138           if(toX > fromX) SendToProgram("O-O\n", cps);
5139           else SendToProgram("O-O-O\n", cps);
5140         }
5141         else SendToProgram(moveList[moveNum], cps);
5142       } else
5143       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5144         char *m = moveList[moveNum];
5145         if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
5146           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5147                                                m[2], m[3] - '0',
5148                                                m[5], m[6] - '0',
5149                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5150         else
5151           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5152                                                m[5], m[6] - '0',
5153                                                m[5], m[6] - '0',
5154                                                m[2], m[3] - '0');
5155           SendToProgram(buf, cps);
5156       } else
5157       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5158         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5159           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5160           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5161                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5162         } else
5163           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5164                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5165         SendToProgram(buf, cps);
5166       }
5167       else SendToProgram(moveList[moveNum], cps);
5168       /* End of additions by Tord */
5169     }
5170
5171     /* [HGM] setting up the opening has brought engine in force mode! */
5172     /*       Send 'go' if we are in a mode where machine should play. */
5173     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5174         (gameMode == TwoMachinesPlay   ||
5175 #if ZIPPY
5176          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5177 #endif
5178          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5179         SendToProgram("go\n", cps);
5180   if (appData.debugMode) {
5181     fprintf(debugFP, "(extra)\n");
5182   }
5183     }
5184     setboardSpoiledMachineBlack = 0;
5185 }
5186
5187 void
5188 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5189 {
5190     char user_move[MSG_SIZ];
5191     char suffix[4];
5192
5193     if(gameInfo.variant == VariantSChess && promoChar) {
5194         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5195         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5196     } else suffix[0] = NULLCHAR;
5197
5198     switch (moveType) {
5199       default:
5200         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5201                 (int)moveType, fromX, fromY, toX, toY);
5202         DisplayError(user_move + strlen("say "), 0);
5203         break;
5204       case WhiteKingSideCastle:
5205       case BlackKingSideCastle:
5206       case WhiteQueenSideCastleWild:
5207       case BlackQueenSideCastleWild:
5208       /* PUSH Fabien */
5209       case WhiteHSideCastleFR:
5210       case BlackHSideCastleFR:
5211       /* POP Fabien */
5212         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5213         break;
5214       case WhiteQueenSideCastle:
5215       case BlackQueenSideCastle:
5216       case WhiteKingSideCastleWild:
5217       case BlackKingSideCastleWild:
5218       /* PUSH Fabien */
5219       case WhiteASideCastleFR:
5220       case BlackASideCastleFR:
5221       /* POP Fabien */
5222         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5223         break;
5224       case WhiteNonPromotion:
5225       case BlackNonPromotion:
5226         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5227         break;
5228       case WhitePromotion:
5229       case BlackPromotion:
5230         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5231            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5232           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5233                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5234                 PieceToChar(WhiteFerz));
5235         else if(gameInfo.variant == VariantGreat)
5236           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5237                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5238                 PieceToChar(WhiteMan));
5239         else
5240           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5241                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5242                 promoChar);
5243         break;
5244       case WhiteDrop:
5245       case BlackDrop:
5246       drop:
5247         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5248                  ToUpper(PieceToChar((ChessSquare) fromX)),
5249                  AAA + toX, ONE + toY);
5250         break;
5251       case IllegalMove:  /* could be a variant we don't quite understand */
5252         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5253       case NormalMove:
5254       case WhiteCapturesEnPassant:
5255       case BlackCapturesEnPassant:
5256         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5257                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5258         break;
5259     }
5260     SendToICS(user_move);
5261     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5262         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5263 }
5264
5265 void
5266 UploadGameEvent ()
5267 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5268     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5269     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5270     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5271       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5272       return;
5273     }
5274     if(gameMode != IcsExamining) { // is this ever not the case?
5275         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5276
5277         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5278           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5279         } else { // on FICS we must first go to general examine mode
5280           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5281         }
5282         if(gameInfo.variant != VariantNormal) {
5283             // try figure out wild number, as xboard names are not always valid on ICS
5284             for(i=1; i<=36; i++) {
5285               snprintf(buf, MSG_SIZ, "wild/%d", i);
5286                 if(StringToVariant(buf) == gameInfo.variant) break;
5287             }
5288             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5289             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5290             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5291         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5292         SendToICS(ics_prefix);
5293         SendToICS(buf);
5294         if(startedFromSetupPosition || backwardMostMove != 0) {
5295           fen = PositionToFEN(backwardMostMove, NULL, 1);
5296           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5297             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5298             SendToICS(buf);
5299           } else { // FICS: everything has to set by separate bsetup commands
5300             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5301             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5302             SendToICS(buf);
5303             if(!WhiteOnMove(backwardMostMove)) {
5304                 SendToICS("bsetup tomove black\n");
5305             }
5306             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5307             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5308             SendToICS(buf);
5309             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5310             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5311             SendToICS(buf);
5312             i = boards[backwardMostMove][EP_STATUS];
5313             if(i >= 0) { // set e.p.
5314               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5315                 SendToICS(buf);
5316             }
5317             bsetup++;
5318           }
5319         }
5320       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5321             SendToICS("bsetup done\n"); // switch to normal examining.
5322     }
5323     for(i = backwardMostMove; i<last; i++) {
5324         char buf[20];
5325         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5326         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5327             int len = strlen(moveList[i]);
5328             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5329             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5330         }
5331         SendToICS(buf);
5332     }
5333     SendToICS(ics_prefix);
5334     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5335 }
5336
5337 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5338 int legNr = 1;
5339
5340 void
5341 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5342 {
5343     if (rf == DROP_RANK) {
5344       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5345       sprintf(move, "%c@%c%c\n",
5346                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5347     } else {
5348         if (promoChar == 'x' || promoChar == NULLCHAR) {
5349           sprintf(move, "%c%c%c%c\n",
5350                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5351           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5352         } else {
5353             sprintf(move, "%c%c%c%c%c\n",
5354                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5355         }
5356     }
5357 }
5358
5359 void
5360 ProcessICSInitScript (FILE *f)
5361 {
5362     char buf[MSG_SIZ];
5363
5364     while (fgets(buf, MSG_SIZ, f)) {
5365         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5366     }
5367
5368     fclose(f);
5369 }
5370
5371
5372 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5373 int dragging;
5374 static ClickType lastClickType;
5375
5376 int
5377 Partner (ChessSquare *p)
5378 { // change piece into promotion partner if one shogi-promotes to the other
5379   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5380   ChessSquare partner;
5381   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5382   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5383   *p = partner;
5384   return 1;
5385 }
5386
5387 void
5388 Sweep (int step)
5389 {
5390     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5391     static int toggleFlag;
5392     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5393     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5394     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5395     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5396     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5397     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5398     do {
5399         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5400         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5401         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5402         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5403         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5404         if(!step) step = -1;
5405     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5406             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5407             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5408             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5409     if(toX >= 0) {
5410         int victim = boards[currentMove][toY][toX];
5411         boards[currentMove][toY][toX] = promoSweep;
5412         DrawPosition(FALSE, boards[currentMove]);
5413         boards[currentMove][toY][toX] = victim;
5414     } else
5415     ChangeDragPiece(promoSweep);
5416 }
5417
5418 int
5419 PromoScroll (int x, int y)
5420 {
5421   int step = 0;
5422
5423   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5424   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5425   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5426   if(!step) return FALSE;
5427   lastX = x; lastY = y;
5428   if((promoSweep < BlackPawn) == flipView) step = -step;
5429   if(step > 0) selectFlag = 1;
5430   if(!selectFlag) Sweep(step);
5431   return FALSE;
5432 }
5433
5434 void
5435 NextPiece (int step)
5436 {
5437     ChessSquare piece = boards[currentMove][toY][toX];
5438     do {
5439         pieceSweep -= step;
5440         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5441         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5442         if(!step) step = -1;
5443     } while(PieceToChar(pieceSweep) == '.');
5444     boards[currentMove][toY][toX] = pieceSweep;
5445     DrawPosition(FALSE, boards[currentMove]);
5446     boards[currentMove][toY][toX] = piece;
5447 }
5448 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5449 void
5450 AlphaRank (char *move, int n)
5451 {
5452 //    char *p = move, c; int x, y;
5453
5454     if (appData.debugMode) {
5455         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5456     }
5457
5458     if(move[1]=='*' &&
5459        move[2]>='0' && move[2]<='9' &&
5460        move[3]>='a' && move[3]<='x'    ) {
5461         move[1] = '@';
5462         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5463         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5464     } else
5465     if(move[0]>='0' && move[0]<='9' &&
5466        move[1]>='a' && move[1]<='x' &&
5467        move[2]>='0' && move[2]<='9' &&
5468        move[3]>='a' && move[3]<='x'    ) {
5469         /* input move, Shogi -> normal */
5470         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5471         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5472         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5473         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5474     } else
5475     if(move[1]=='@' &&
5476        move[3]>='0' && move[3]<='9' &&
5477        move[2]>='a' && move[2]<='x'    ) {
5478         move[1] = '*';
5479         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5480         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5481     } else
5482     if(
5483        move[0]>='a' && move[0]<='x' &&
5484        move[3]>='0' && move[3]<='9' &&
5485        move[2]>='a' && move[2]<='x'    ) {
5486          /* output move, normal -> Shogi */
5487         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5488         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5489         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5490         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5491         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5492     }
5493     if (appData.debugMode) {
5494         fprintf(debugFP, "   out = '%s'\n", move);
5495     }
5496 }
5497
5498 char yy_textstr[8000];
5499
5500 /* Parser for moves from gnuchess, ICS, or user typein box */
5501 Boolean
5502 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5503 {
5504     int badFrom;
5505     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5506
5507     switch (*moveType) {
5508       case WhitePromotion:
5509       case BlackPromotion:
5510       case WhiteNonPromotion:
5511       case BlackNonPromotion:
5512       case NormalMove:
5513       case FirstLeg:
5514       case WhiteCapturesEnPassant:
5515       case BlackCapturesEnPassant:
5516       case WhiteKingSideCastle:
5517       case WhiteQueenSideCastle:
5518       case BlackKingSideCastle:
5519       case BlackQueenSideCastle:
5520       case WhiteKingSideCastleWild:
5521       case WhiteQueenSideCastleWild:
5522       case BlackKingSideCastleWild:
5523       case BlackQueenSideCastleWild:
5524       /* Code added by Tord: */
5525       case WhiteHSideCastleFR:
5526       case WhiteASideCastleFR:
5527       case BlackHSideCastleFR:
5528       case BlackASideCastleFR:
5529       /* End of code added by Tord */
5530       case IllegalMove:         /* bug or odd chess variant */
5531         *fromX = currentMoveString[0] - AAA;
5532         *fromY = currentMoveString[1] - ONE;
5533         *toX = currentMoveString[2] - AAA;
5534         *toY = currentMoveString[3] - ONE;
5535         *promoChar = currentMoveString[4];
5536         badFrom = (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT);
5537         if(currentMoveString[1] == '@') { badFrom = FALSE; *fromX = CharToPiece(currentMoveString[0]); *fromY = DROP_RANK; } // illegal drop
5538         if (badFrom ||
5539             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5540     if (appData.debugMode) {
5541         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5542     }
5543             *fromX = *fromY = *toX = *toY = 0;
5544             return FALSE;
5545         }
5546         if (appData.testLegality) {
5547           return (*moveType != IllegalMove);
5548         } else {
5549           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5550                          // [HGM] lion: if this is a double move we are less critical
5551                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5552         }
5553
5554       case WhiteDrop:
5555       case BlackDrop:
5556         *fromX = *moveType == WhiteDrop ?
5557           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5558           (int) CharToPiece(ToLower(currentMoveString[0]));
5559         *fromY = DROP_RANK;
5560         *toX = currentMoveString[2] - AAA;
5561         *toY = currentMoveString[3] - ONE;
5562         *promoChar = NULLCHAR;
5563         return TRUE;
5564
5565       case AmbiguousMove:
5566       case ImpossibleMove:
5567       case EndOfFile:
5568       case ElapsedTime:
5569       case Comment:
5570       case PGNTag:
5571       case NAG:
5572       case WhiteWins:
5573       case BlackWins:
5574       case GameIsDrawn:
5575       default:
5576     if (appData.debugMode) {
5577         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5578     }
5579         /* bug? */
5580         *fromX = *fromY = *toX = *toY = 0;
5581         *promoChar = NULLCHAR;
5582         return FALSE;
5583     }
5584 }
5585
5586 Boolean pushed = FALSE;
5587 char *lastParseAttempt;
5588
5589 void
5590 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5591 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5592   int fromX, fromY, toX, toY; char promoChar;
5593   ChessMove moveType;
5594   Boolean valid;
5595   int nr = 0;
5596
5597   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5598   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5599     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5600     pushed = TRUE;
5601   }
5602   endPV = forwardMostMove;
5603   do {
5604     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5605     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5606     lastParseAttempt = pv;
5607     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5608     if(!valid && nr == 0 &&
5609        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5610         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5611         // Hande case where played move is different from leading PV move
5612         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5613         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5614         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5615         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5616           endPV += 2; // if position different, keep this
5617           moveList[endPV-1][0] = fromX + AAA;
5618           moveList[endPV-1][1] = fromY + ONE;
5619           moveList[endPV-1][2] = toX + AAA;
5620           moveList[endPV-1][3] = toY + ONE;
5621           parseList[endPV-1][0] = NULLCHAR;
5622           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5623         }
5624       }
5625     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5626     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5627     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5628     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5629         valid++; // allow comments in PV
5630         continue;
5631     }
5632     nr++;
5633     if(endPV+1 > framePtr) break; // no space, truncate
5634     if(!valid) break;
5635     endPV++;
5636     CopyBoard(boards[endPV], boards[endPV-1]);
5637     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5638     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5639     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5640     CoordsToAlgebraic(boards[endPV - 1],
5641                              PosFlags(endPV - 1),
5642                              fromY, fromX, toY, toX, promoChar,
5643                              parseList[endPV - 1]);
5644   } while(valid);
5645   if(atEnd == 2) return; // used hidden, for PV conversion
5646   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5647   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5648   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5649                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5650   DrawPosition(TRUE, boards[currentMove]);
5651 }
5652
5653 int
5654 MultiPV (ChessProgramState *cps)
5655 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5656         int i;
5657         for(i=0; i<cps->nrOptions; i++)
5658             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5659                 return i;
5660         return -1;
5661 }
5662
5663 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5664
5665 Boolean
5666 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5667 {
5668         int startPV, multi, lineStart, origIndex = index;
5669         char *p, buf2[MSG_SIZ];
5670         ChessProgramState *cps = (pane ? &second : &first);
5671
5672         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5673         lastX = x; lastY = y;
5674         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5675         lineStart = startPV = index;
5676         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5677         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5678         index = startPV;
5679         do{ while(buf[index] && buf[index] != '\n') index++;
5680         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5681         buf[index] = 0;
5682         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5683                 int n = cps->option[multi].value;
5684                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5685                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5686                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5687                 cps->option[multi].value = n;
5688                 *start = *end = 0;
5689                 return FALSE;
5690         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5691                 ExcludeClick(origIndex - lineStart);
5692                 return FALSE;
5693         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5694                 Collapse(origIndex - lineStart);
5695                 return FALSE;
5696         }
5697         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5698         *start = startPV; *end = index-1;
5699         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5700         return TRUE;
5701 }
5702
5703 char *
5704 PvToSAN (char *pv)
5705 {
5706         static char buf[10*MSG_SIZ];
5707         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5708         *buf = NULLCHAR;
5709         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5710         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5711         for(i = forwardMostMove; i<endPV; i++){
5712             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5713             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5714             k += strlen(buf+k);
5715         }
5716         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5717         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5718         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5719         endPV = savedEnd;
5720         return buf;
5721 }
5722
5723 Boolean
5724 LoadPV (int x, int y)
5725 { // called on right mouse click to load PV
5726   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5727   lastX = x; lastY = y;
5728   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5729   extendGame = FALSE;
5730   return TRUE;
5731 }
5732
5733 void
5734 UnLoadPV ()
5735 {
5736   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5737   if(endPV < 0) return;
5738   if(appData.autoCopyPV) CopyFENToClipboard();
5739   endPV = -1;
5740   if(extendGame && currentMove > forwardMostMove) {
5741         Boolean saveAnimate = appData.animate;
5742         if(pushed) {
5743             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5744                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5745             } else storedGames--; // abandon shelved tail of original game
5746         }
5747         pushed = FALSE;
5748         forwardMostMove = currentMove;
5749         currentMove = oldFMM;
5750         appData.animate = FALSE;
5751         ToNrEvent(forwardMostMove);
5752         appData.animate = saveAnimate;
5753   }
5754   currentMove = forwardMostMove;
5755   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5756   ClearPremoveHighlights();
5757   DrawPosition(TRUE, boards[currentMove]);
5758 }
5759
5760 void
5761 MovePV (int x, int y, int h)
5762 { // step through PV based on mouse coordinates (called on mouse move)
5763   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5764
5765   // we must somehow check if right button is still down (might be released off board!)
5766   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5767   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5768   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5769   if(!step) return;
5770   lastX = x; lastY = y;
5771
5772   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5773   if(endPV < 0) return;
5774   if(y < margin) step = 1; else
5775   if(y > h - margin) step = -1;
5776   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5777   currentMove += step;
5778   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5779   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5780                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5781   DrawPosition(FALSE, boards[currentMove]);
5782 }
5783
5784
5785 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5786 // All positions will have equal probability, but the current method will not provide a unique
5787 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5788 #define DARK 1
5789 #define LITE 2
5790 #define ANY 3
5791
5792 int squaresLeft[4];
5793 int piecesLeft[(int)BlackPawn];
5794 int seed, nrOfShuffles;
5795
5796 void
5797 GetPositionNumber ()
5798 {       // sets global variable seed
5799         int i;
5800
5801         seed = appData.defaultFrcPosition;
5802         if(seed < 0) { // randomize based on time for negative FRC position numbers
5803                 for(i=0; i<50; i++) seed += random();
5804                 seed = random() ^ random() >> 8 ^ random() << 8;
5805                 if(seed<0) seed = -seed;
5806         }
5807 }
5808
5809 int
5810 put (Board board, int pieceType, int rank, int n, int shade)
5811 // put the piece on the (n-1)-th empty squares of the given shade
5812 {
5813         int i;
5814
5815         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5816                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5817                         board[rank][i] = (ChessSquare) pieceType;
5818                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5819                         squaresLeft[ANY]--;
5820                         piecesLeft[pieceType]--;
5821                         return i;
5822                 }
5823         }
5824         return -1;
5825 }
5826
5827
5828 void
5829 AddOnePiece (Board board, int pieceType, int rank, int shade)
5830 // calculate where the next piece goes, (any empty square), and put it there
5831 {
5832         int i;
5833
5834         i = seed % squaresLeft[shade];
5835         nrOfShuffles *= squaresLeft[shade];
5836         seed /= squaresLeft[shade];
5837         put(board, pieceType, rank, i, shade);
5838 }
5839
5840 void
5841 AddTwoPieces (Board board, int pieceType, int rank)
5842 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5843 {
5844         int i, n=squaresLeft[ANY], j=n-1, k;
5845
5846         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5847         i = seed % k;  // pick one
5848         nrOfShuffles *= k;
5849         seed /= k;
5850         while(i >= j) i -= j--;
5851         j = n - 1 - j; i += j;
5852         put(board, pieceType, rank, j, ANY);
5853         put(board, pieceType, rank, i, ANY);
5854 }
5855
5856 void
5857 SetUpShuffle (Board board, int number)
5858 {
5859         int i, p, first=1;
5860
5861         GetPositionNumber(); nrOfShuffles = 1;
5862
5863         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5864         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5865         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5866
5867         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5868
5869         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5870             p = (int) board[0][i];
5871             if(p < (int) BlackPawn) piecesLeft[p] ++;
5872             board[0][i] = EmptySquare;
5873         }
5874
5875         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5876             // shuffles restricted to allow normal castling put KRR first
5877             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5878                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5879             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5880                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5881             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5882                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5883             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5884                 put(board, WhiteRook, 0, 0, ANY);
5885             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5886         }
5887
5888         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5889             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5890             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5891                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5892                 while(piecesLeft[p] >= 2) {
5893                     AddOnePiece(board, p, 0, LITE);
5894                     AddOnePiece(board, p, 0, DARK);
5895                 }
5896                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5897             }
5898
5899         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5900             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5901             // but we leave King and Rooks for last, to possibly obey FRC restriction
5902             if(p == (int)WhiteRook) continue;
5903             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5904             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5905         }
5906
5907         // now everything is placed, except perhaps King (Unicorn) and Rooks
5908
5909         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5910             // Last King gets castling rights
5911             while(piecesLeft[(int)WhiteUnicorn]) {
5912                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5913                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5914             }
5915
5916             while(piecesLeft[(int)WhiteKing]) {
5917                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5918                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5919             }
5920
5921
5922         } else {
5923             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5924             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5925         }
5926
5927         // Only Rooks can be left; simply place them all
5928         while(piecesLeft[(int)WhiteRook]) {
5929                 i = put(board, WhiteRook, 0, 0, ANY);
5930                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5931                         if(first) {
5932                                 first=0;
5933                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5934                         }
5935                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5936                 }
5937         }
5938         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5939             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5940         }
5941
5942         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5943 }
5944
5945 int
5946 SetCharTable (char *table, const char * map)
5947 /* [HGM] moved here from winboard.c because of its general usefulness */
5948 /*       Basically a safe strcpy that uses the last character as King */
5949 {
5950     int result = FALSE; int NrPieces;
5951
5952     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5953                     && NrPieces >= 12 && !(NrPieces&1)) {
5954         int i; /* [HGM] Accept even length from 12 to 34 */
5955
5956         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5957         for( i=0; i<NrPieces/2-1; i++ ) {
5958             table[i] = map[i];
5959             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5960         }
5961         table[(int) WhiteKing]  = map[NrPieces/2-1];
5962         table[(int) BlackKing]  = map[NrPieces-1];
5963
5964         result = TRUE;
5965     }
5966
5967     return result;
5968 }
5969
5970 void
5971 Prelude (Board board)
5972 {       // [HGM] superchess: random selection of exo-pieces
5973         int i, j, k; ChessSquare p;
5974         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5975
5976         GetPositionNumber(); // use FRC position number
5977
5978         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5979             SetCharTable(pieceToChar, appData.pieceToCharTable);
5980             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5981                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5982         }
5983
5984         j = seed%4;                 seed /= 4;
5985         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5986         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5987         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5988         j = seed%3 + (seed%3 >= j); seed /= 3;
5989         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5990         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5991         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5992         j = seed%3;                 seed /= 3;
5993         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5994         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5995         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5996         j = seed%2 + (seed%2 >= j); seed /= 2;
5997         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5998         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5999         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6000         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6001         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6002         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6003         put(board, exoPieces[0],    0, 0, ANY);
6004         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6005 }
6006
6007 void
6008 InitPosition (int redraw)
6009 {
6010     ChessSquare (* pieces)[BOARD_FILES];
6011     int i, j, pawnRow=1, pieceRows=1, overrule,
6012     oldx = gameInfo.boardWidth,
6013     oldy = gameInfo.boardHeight,
6014     oldh = gameInfo.holdingsWidth;
6015     static int oldv;
6016
6017     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6018
6019     /* [AS] Initialize pv info list [HGM] and game status */
6020     {
6021         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6022             pvInfoList[i].depth = 0;
6023             boards[i][EP_STATUS] = EP_NONE;
6024             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6025         }
6026
6027         initialRulePlies = 0; /* 50-move counter start */
6028
6029         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6030         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6031     }
6032
6033
6034     /* [HGM] logic here is completely changed. In stead of full positions */
6035     /* the initialized data only consist of the two backranks. The switch */
6036     /* selects which one we will use, which is than copied to the Board   */
6037     /* initialPosition, which for the rest is initialized by Pawns and    */
6038     /* empty squares. This initial position is then copied to boards[0],  */
6039     /* possibly after shuffling, so that it remains available.            */
6040
6041     gameInfo.holdingsWidth = 0; /* default board sizes */
6042     gameInfo.boardWidth    = 8;
6043     gameInfo.boardHeight   = 8;
6044     gameInfo.holdingsSize  = 0;
6045     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6046     for(i=0; i<BOARD_FILES-6; i++)
6047       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6048     initialPosition[EP_STATUS] = EP_NONE;
6049     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6050     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6051     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6052          SetCharTable(pieceNickName, appData.pieceNickNames);
6053     else SetCharTable(pieceNickName, "............");
6054     pieces = FIDEArray;
6055
6056     switch (gameInfo.variant) {
6057     case VariantFischeRandom:
6058       shuffleOpenings = TRUE;
6059       appData.fischerCastling = TRUE;
6060     default:
6061       break;
6062     case VariantShatranj:
6063       pieces = ShatranjArray;
6064       nrCastlingRights = 0;
6065       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6066       break;
6067     case VariantMakruk:
6068       pieces = makrukArray;
6069       nrCastlingRights = 0;
6070       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6071       break;
6072     case VariantASEAN:
6073       pieces = aseanArray;
6074       nrCastlingRights = 0;
6075       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6076       break;
6077     case VariantTwoKings:
6078       pieces = twoKingsArray;
6079       break;
6080     case VariantGrand:
6081       pieces = GrandArray;
6082       nrCastlingRights = 0;
6083       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6084       gameInfo.boardWidth = 10;
6085       gameInfo.boardHeight = 10;
6086       gameInfo.holdingsSize = 7;
6087       break;
6088     case VariantCapaRandom:
6089       shuffleOpenings = TRUE;
6090       appData.fischerCastling = TRUE;
6091     case VariantCapablanca:
6092       pieces = CapablancaArray;
6093       gameInfo.boardWidth = 10;
6094       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6095       break;
6096     case VariantGothic:
6097       pieces = GothicArray;
6098       gameInfo.boardWidth = 10;
6099       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6100       break;
6101     case VariantSChess:
6102       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6103       gameInfo.holdingsSize = 7;
6104       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6105       break;
6106     case VariantJanus:
6107       pieces = JanusArray;
6108       gameInfo.boardWidth = 10;
6109       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6110       nrCastlingRights = 6;
6111         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6112         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6113         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6114         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6115         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6116         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6117       break;
6118     case VariantFalcon:
6119       pieces = FalconArray;
6120       gameInfo.boardWidth = 10;
6121       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6122       break;
6123     case VariantXiangqi:
6124       pieces = XiangqiArray;
6125       gameInfo.boardWidth  = 9;
6126       gameInfo.boardHeight = 10;
6127       nrCastlingRights = 0;
6128       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6129       break;
6130     case VariantShogi:
6131       pieces = ShogiArray;
6132       gameInfo.boardWidth  = 9;
6133       gameInfo.boardHeight = 9;
6134       gameInfo.holdingsSize = 7;
6135       nrCastlingRights = 0;
6136       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6137       break;
6138     case VariantChu:
6139       pieces = ChuArray; pieceRows = 3;
6140       gameInfo.boardWidth  = 12;
6141       gameInfo.boardHeight = 12;
6142       nrCastlingRights = 0;
6143       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6144                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6145       break;
6146     case VariantCourier:
6147       pieces = CourierArray;
6148       gameInfo.boardWidth  = 12;
6149       nrCastlingRights = 0;
6150       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6151       break;
6152     case VariantKnightmate:
6153       pieces = KnightmateArray;
6154       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6155       break;
6156     case VariantSpartan:
6157       pieces = SpartanArray;
6158       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6159       break;
6160     case VariantLion:
6161       pieces = lionArray;
6162       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6163       break;
6164     case VariantChuChess:
6165       pieces = ChuChessArray;
6166       gameInfo.boardWidth = 10;
6167       gameInfo.boardHeight = 10;
6168       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6169       break;
6170     case VariantFairy:
6171       pieces = fairyArray;
6172       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6173       break;
6174     case VariantGreat:
6175       pieces = GreatArray;
6176       gameInfo.boardWidth = 10;
6177       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6178       gameInfo.holdingsSize = 8;
6179       break;
6180     case VariantSuper:
6181       pieces = FIDEArray;
6182       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6183       gameInfo.holdingsSize = 8;
6184       startedFromSetupPosition = TRUE;
6185       break;
6186     case VariantCrazyhouse:
6187     case VariantBughouse:
6188       pieces = FIDEArray;
6189       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6190       gameInfo.holdingsSize = 5;
6191       break;
6192     case VariantWildCastle:
6193       pieces = FIDEArray;
6194       /* !!?shuffle with kings guaranteed to be on d or e file */
6195       shuffleOpenings = 1;
6196       break;
6197     case VariantNoCastle:
6198       pieces = FIDEArray;
6199       nrCastlingRights = 0;
6200       /* !!?unconstrained back-rank shuffle */
6201       shuffleOpenings = 1;
6202       break;
6203     }
6204
6205     overrule = 0;
6206     if(appData.NrFiles >= 0) {
6207         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6208         gameInfo.boardWidth = appData.NrFiles;
6209     }
6210     if(appData.NrRanks >= 0) {
6211         gameInfo.boardHeight = appData.NrRanks;
6212     }
6213     if(appData.holdingsSize >= 0) {
6214         i = appData.holdingsSize;
6215         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6216         gameInfo.holdingsSize = i;
6217     }
6218     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6219     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6220         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6221
6222     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6223     if(pawnRow < 1) pawnRow = 1;
6224     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6225        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6226     if(gameInfo.variant == VariantChu) pawnRow = 3;
6227
6228     /* User pieceToChar list overrules defaults */
6229     if(appData.pieceToCharTable != NULL)
6230         SetCharTable(pieceToChar, appData.pieceToCharTable);
6231
6232     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6233
6234         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6235             s = (ChessSquare) 0; /* account holding counts in guard band */
6236         for( i=0; i<BOARD_HEIGHT; i++ )
6237             initialPosition[i][j] = s;
6238
6239         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6240         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6241         initialPosition[pawnRow][j] = WhitePawn;
6242         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6243         if(gameInfo.variant == VariantXiangqi) {
6244             if(j&1) {
6245                 initialPosition[pawnRow][j] =
6246                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6247                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6248                    initialPosition[2][j] = WhiteCannon;
6249                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6250                 }
6251             }
6252         }
6253         if(gameInfo.variant == VariantChu) {
6254              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6255                initialPosition[pawnRow+1][j] = WhiteCobra,
6256                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6257              for(i=1; i<pieceRows; i++) {
6258                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6259                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6260              }
6261         }
6262         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6263             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6264                initialPosition[0][j] = WhiteRook;
6265                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6266             }
6267         }
6268         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6269     }
6270     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6271     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6272
6273             j=BOARD_LEFT+1;
6274             initialPosition[1][j] = WhiteBishop;
6275             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6276             j=BOARD_RGHT-2;
6277             initialPosition[1][j] = WhiteRook;
6278             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6279     }
6280
6281     if( nrCastlingRights == -1) {
6282         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6283         /*       This sets default castling rights from none to normal corners   */
6284         /* Variants with other castling rights must set them themselves above    */
6285         nrCastlingRights = 6;
6286
6287         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6288         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6289         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6290         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6291         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6292         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6293      }
6294
6295      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6296      if(gameInfo.variant == VariantGreat) { // promotion commoners
6297         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6298         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6299         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6300         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6301      }
6302      if( gameInfo.variant == VariantSChess ) {
6303       initialPosition[1][0] = BlackMarshall;
6304       initialPosition[2][0] = BlackAngel;
6305       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6306       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6307       initialPosition[1][1] = initialPosition[2][1] =
6308       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6309      }
6310   if (appData.debugMode) {
6311     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6312   }
6313     if(shuffleOpenings) {
6314         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6315         startedFromSetupPosition = TRUE;
6316     }
6317     if(startedFromPositionFile) {
6318       /* [HGM] loadPos: use PositionFile for every new game */
6319       CopyBoard(initialPosition, filePosition);
6320       for(i=0; i<nrCastlingRights; i++)
6321           initialRights[i] = filePosition[CASTLING][i];
6322       startedFromSetupPosition = TRUE;
6323     }
6324
6325     CopyBoard(boards[0], initialPosition);
6326
6327     if(oldx != gameInfo.boardWidth ||
6328        oldy != gameInfo.boardHeight ||
6329        oldv != gameInfo.variant ||
6330        oldh != gameInfo.holdingsWidth
6331                                          )
6332             InitDrawingSizes(-2 ,0);
6333
6334     oldv = gameInfo.variant;
6335     if (redraw)
6336       DrawPosition(TRUE, boards[currentMove]);
6337 }
6338
6339 void
6340 SendBoard (ChessProgramState *cps, int moveNum)
6341 {
6342     char message[MSG_SIZ];
6343
6344     if (cps->useSetboard) {
6345       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6346       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6347       SendToProgram(message, cps);
6348       free(fen);
6349
6350     } else {
6351       ChessSquare *bp;
6352       int i, j, left=0, right=BOARD_WIDTH;
6353       /* Kludge to set black to move, avoiding the troublesome and now
6354        * deprecated "black" command.
6355        */
6356       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6357         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6358
6359       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6360
6361       SendToProgram("edit\n", cps);
6362       SendToProgram("#\n", cps);
6363       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6364         bp = &boards[moveNum][i][left];
6365         for (j = left; j < right; j++, bp++) {
6366           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6367           if ((int) *bp < (int) BlackPawn) {
6368             if(j == BOARD_RGHT+1)
6369                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6370             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6371             if(message[0] == '+' || message[0] == '~') {
6372               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6373                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6374                         AAA + j, ONE + i);
6375             }
6376             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6377                 message[1] = BOARD_RGHT   - 1 - j + '1';
6378                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6379             }
6380             SendToProgram(message, cps);
6381           }
6382         }
6383       }
6384
6385       SendToProgram("c\n", cps);
6386       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6387         bp = &boards[moveNum][i][left];
6388         for (j = left; j < right; j++, bp++) {
6389           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6390           if (((int) *bp != (int) EmptySquare)
6391               && ((int) *bp >= (int) BlackPawn)) {
6392             if(j == BOARD_LEFT-2)
6393                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6394             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6395                     AAA + j, ONE + i);
6396             if(message[0] == '+' || message[0] == '~') {
6397               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6398                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6399                         AAA + j, ONE + i);
6400             }
6401             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6402                 message[1] = BOARD_RGHT   - 1 - j + '1';
6403                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6404             }
6405             SendToProgram(message, cps);
6406           }
6407         }
6408       }
6409
6410       SendToProgram(".\n", cps);
6411     }
6412     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6413 }
6414
6415 char exclusionHeader[MSG_SIZ];
6416 int exCnt, excludePtr;
6417 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6418 static Exclusion excluTab[200];
6419 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6420
6421 static void
6422 WriteMap (int s)
6423 {
6424     int j;
6425     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6426     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6427 }
6428
6429 static void
6430 ClearMap ()
6431 {
6432     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6433     excludePtr = 24; exCnt = 0;
6434     WriteMap(0);
6435 }
6436
6437 static void
6438 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6439 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6440     char buf[2*MOVE_LEN], *p;
6441     Exclusion *e = excluTab;
6442     int i;
6443     for(i=0; i<exCnt; i++)
6444         if(e[i].ff == fromX && e[i].fr == fromY &&
6445            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6446     if(i == exCnt) { // was not in exclude list; add it
6447         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6448         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6449             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6450             return; // abort
6451         }
6452         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6453         excludePtr++; e[i].mark = excludePtr++;
6454         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6455         exCnt++;
6456     }
6457     exclusionHeader[e[i].mark] = state;
6458 }
6459
6460 static int
6461 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6462 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6463     char buf[MSG_SIZ];
6464     int j, k;
6465     ChessMove moveType;
6466     if((signed char)promoChar == -1) { // kludge to indicate best move
6467         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6468             return 1; // if unparsable, abort
6469     }
6470     // update exclusion map (resolving toggle by consulting existing state)
6471     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6472     j = k%8; k >>= 3;
6473     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6474     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6475          excludeMap[k] |=   1<<j;
6476     else excludeMap[k] &= ~(1<<j);
6477     // update header
6478     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6479     // inform engine
6480     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6481     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6482     SendToBoth(buf);
6483     return (state == '+');
6484 }
6485
6486 static void
6487 ExcludeClick (int index)
6488 {
6489     int i, j;
6490     Exclusion *e = excluTab;
6491     if(index < 25) { // none, best or tail clicked
6492         if(index < 13) { // none: include all
6493             WriteMap(0); // clear map
6494             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6495             SendToBoth("include all\n"); // and inform engine
6496         } else if(index > 18) { // tail
6497             if(exclusionHeader[19] == '-') { // tail was excluded
6498                 SendToBoth("include all\n");
6499                 WriteMap(0); // clear map completely
6500                 // now re-exclude selected moves
6501                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6502                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6503             } else { // tail was included or in mixed state
6504                 SendToBoth("exclude all\n");
6505                 WriteMap(0xFF); // fill map completely
6506                 // now re-include selected moves
6507                 j = 0; // count them
6508                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6509                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6510                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6511             }
6512         } else { // best
6513             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6514         }
6515     } else {
6516         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6517             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6518             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6519             break;
6520         }
6521     }
6522 }
6523
6524 ChessSquare
6525 DefaultPromoChoice (int white)
6526 {
6527     ChessSquare result;
6528     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6529        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6530         result = WhiteFerz; // no choice
6531     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6532         result= WhiteKing; // in Suicide Q is the last thing we want
6533     else if(gameInfo.variant == VariantSpartan)
6534         result = white ? WhiteQueen : WhiteAngel;
6535     else result = WhiteQueen;
6536     if(!white) result = WHITE_TO_BLACK result;
6537     return result;
6538 }
6539
6540 static int autoQueen; // [HGM] oneclick
6541
6542 int
6543 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6544 {
6545     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6546     /* [HGM] add Shogi promotions */
6547     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6548     ChessSquare piece, partner;
6549     ChessMove moveType;
6550     Boolean premove;
6551
6552     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6553     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6554
6555     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6556       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6557         return FALSE;
6558
6559     piece = boards[currentMove][fromY][fromX];
6560     if(gameInfo.variant == VariantChu) {
6561         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6562         promotionZoneSize = BOARD_HEIGHT/3;
6563         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6564     } else if(gameInfo.variant == VariantShogi) {
6565         promotionZoneSize = BOARD_HEIGHT/3;
6566         highestPromotingPiece = (int)WhiteAlfil;
6567     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6568         promotionZoneSize = 3;
6569     }
6570
6571     // Treat Lance as Pawn when it is not representing Amazon or Lance
6572     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6573         if(piece == WhiteLance) piece = WhitePawn; else
6574         if(piece == BlackLance) piece = BlackPawn;
6575     }
6576
6577     // next weed out all moves that do not touch the promotion zone at all
6578     if((int)piece >= BlackPawn) {
6579         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6580              return FALSE;
6581         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6582         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6583     } else {
6584         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6585            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6586         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6587              return FALSE;
6588     }
6589
6590     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6591
6592     // weed out mandatory Shogi promotions
6593     if(gameInfo.variant == VariantShogi) {
6594         if(piece >= BlackPawn) {
6595             if(toY == 0 && piece == BlackPawn ||
6596                toY == 0 && piece == BlackQueen ||
6597                toY <= 1 && piece == BlackKnight) {
6598                 *promoChoice = '+';
6599                 return FALSE;
6600             }
6601         } else {
6602             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6603                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6604                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6605                 *promoChoice = '+';
6606                 return FALSE;
6607             }
6608         }
6609     }
6610
6611     // weed out obviously illegal Pawn moves
6612     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6613         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6614         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6615         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6616         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6617         // note we are not allowed to test for valid (non-)capture, due to premove
6618     }
6619
6620     // we either have a choice what to promote to, or (in Shogi) whether to promote
6621     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6622        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6623         ChessSquare p=BlackFerz;  // no choice
6624         while(p < EmptySquare) {  //but make sure we use piece that exists
6625             *promoChoice = PieceToChar(p++);
6626             if(*promoChoice != '.') break;
6627         }
6628         return FALSE;
6629     }
6630     // no sense asking what we must promote to if it is going to explode...
6631     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6632         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6633         return FALSE;
6634     }
6635     // give caller the default choice even if we will not make it
6636     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6637     partner = piece; // pieces can promote if the pieceToCharTable says so
6638     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6639     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6640     if(        sweepSelect && gameInfo.variant != VariantGreat
6641                            && gameInfo.variant != VariantGrand
6642                            && gameInfo.variant != VariantSuper) return FALSE;
6643     if(autoQueen) return FALSE; // predetermined
6644
6645     // suppress promotion popup on illegal moves that are not premoves
6646     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6647               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6648     if(appData.testLegality && !premove) {
6649         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6650                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6651         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6652         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6653             return FALSE;
6654     }
6655
6656     return TRUE;
6657 }
6658
6659 int
6660 InPalace (int row, int column)
6661 {   /* [HGM] for Xiangqi */
6662     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6663          column < (BOARD_WIDTH + 4)/2 &&
6664          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6665     return FALSE;
6666 }
6667
6668 int
6669 PieceForSquare (int x, int y)
6670 {
6671   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6672      return -1;
6673   else
6674      return boards[currentMove][y][x];
6675 }
6676
6677 int
6678 OKToStartUserMove (int x, int y)
6679 {
6680     ChessSquare from_piece;
6681     int white_piece;
6682
6683     if (matchMode) return FALSE;
6684     if (gameMode == EditPosition) return TRUE;
6685
6686     if (x >= 0 && y >= 0)
6687       from_piece = boards[currentMove][y][x];
6688     else
6689       from_piece = EmptySquare;
6690
6691     if (from_piece == EmptySquare) return FALSE;
6692
6693     white_piece = (int)from_piece >= (int)WhitePawn &&
6694       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6695
6696     switch (gameMode) {
6697       case AnalyzeFile:
6698       case TwoMachinesPlay:
6699       case EndOfGame:
6700         return FALSE;
6701
6702       case IcsObserving:
6703       case IcsIdle:
6704         return FALSE;
6705
6706       case MachinePlaysWhite:
6707       case IcsPlayingBlack:
6708         if (appData.zippyPlay) return FALSE;
6709         if (white_piece) {
6710             DisplayMoveError(_("You are playing Black"));
6711             return FALSE;
6712         }
6713         break;
6714
6715       case MachinePlaysBlack:
6716       case IcsPlayingWhite:
6717         if (appData.zippyPlay) return FALSE;
6718         if (!white_piece) {
6719             DisplayMoveError(_("You are playing White"));
6720             return FALSE;
6721         }
6722         break;
6723
6724       case PlayFromGameFile:
6725             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6726       case EditGame:
6727         if (!white_piece && WhiteOnMove(currentMove)) {
6728             DisplayMoveError(_("It is White's turn"));
6729             return FALSE;
6730         }
6731         if (white_piece && !WhiteOnMove(currentMove)) {
6732             DisplayMoveError(_("It is Black's turn"));
6733             return FALSE;
6734         }
6735         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6736             /* Editing correspondence game history */
6737             /* Could disallow this or prompt for confirmation */
6738             cmailOldMove = -1;
6739         }
6740         break;
6741
6742       case BeginningOfGame:
6743         if (appData.icsActive) return FALSE;
6744         if (!appData.noChessProgram) {
6745             if (!white_piece) {
6746                 DisplayMoveError(_("You are playing White"));
6747                 return FALSE;
6748             }
6749         }
6750         break;
6751
6752       case Training:
6753         if (!white_piece && WhiteOnMove(currentMove)) {
6754             DisplayMoveError(_("It is White's turn"));
6755             return FALSE;
6756         }
6757         if (white_piece && !WhiteOnMove(currentMove)) {
6758             DisplayMoveError(_("It is Black's turn"));
6759             return FALSE;
6760         }
6761         break;
6762
6763       default:
6764       case IcsExamining:
6765         break;
6766     }
6767     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6768         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6769         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6770         && gameMode != AnalyzeFile && gameMode != Training) {
6771         DisplayMoveError(_("Displayed position is not current"));
6772         return FALSE;
6773     }
6774     return TRUE;
6775 }
6776
6777 Boolean
6778 OnlyMove (int *x, int *y, Boolean captures)
6779 {
6780     DisambiguateClosure cl;
6781     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6782     switch(gameMode) {
6783       case MachinePlaysBlack:
6784       case IcsPlayingWhite:
6785       case BeginningOfGame:
6786         if(!WhiteOnMove(currentMove)) return FALSE;
6787         break;
6788       case MachinePlaysWhite:
6789       case IcsPlayingBlack:
6790         if(WhiteOnMove(currentMove)) return FALSE;
6791         break;
6792       case EditGame:
6793         break;
6794       default:
6795         return FALSE;
6796     }
6797     cl.pieceIn = EmptySquare;
6798     cl.rfIn = *y;
6799     cl.ffIn = *x;
6800     cl.rtIn = -1;
6801     cl.ftIn = -1;
6802     cl.promoCharIn = NULLCHAR;
6803     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6804     if( cl.kind == NormalMove ||
6805         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6806         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6807         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6808       fromX = cl.ff;
6809       fromY = cl.rf;
6810       *x = cl.ft;
6811       *y = cl.rt;
6812       return TRUE;
6813     }
6814     if(cl.kind != ImpossibleMove) return FALSE;
6815     cl.pieceIn = EmptySquare;
6816     cl.rfIn = -1;
6817     cl.ffIn = -1;
6818     cl.rtIn = *y;
6819     cl.ftIn = *x;
6820     cl.promoCharIn = NULLCHAR;
6821     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6822     if( cl.kind == NormalMove ||
6823         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6824         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6825         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6826       fromX = cl.ff;
6827       fromY = cl.rf;
6828       *x = cl.ft;
6829       *y = cl.rt;
6830       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6831       return TRUE;
6832     }
6833     return FALSE;
6834 }
6835
6836 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6837 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6838 int lastLoadGameUseList = FALSE;
6839 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6840 ChessMove lastLoadGameStart = EndOfFile;
6841 int doubleClick;
6842 Boolean addToBookFlag;
6843
6844 void
6845 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6846 {
6847     ChessMove moveType;
6848     ChessSquare pup;
6849     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6850
6851     /* Check if the user is playing in turn.  This is complicated because we
6852        let the user "pick up" a piece before it is his turn.  So the piece he
6853        tried to pick up may have been captured by the time he puts it down!
6854        Therefore we use the color the user is supposed to be playing in this
6855        test, not the color of the piece that is currently on the starting
6856        square---except in EditGame mode, where the user is playing both
6857        sides; fortunately there the capture race can't happen.  (It can
6858        now happen in IcsExamining mode, but that's just too bad.  The user
6859        will get a somewhat confusing message in that case.)
6860        */
6861
6862     switch (gameMode) {
6863       case AnalyzeFile:
6864       case TwoMachinesPlay:
6865       case EndOfGame:
6866       case IcsObserving:
6867       case IcsIdle:
6868         /* We switched into a game mode where moves are not accepted,
6869            perhaps while the mouse button was down. */
6870         return;
6871
6872       case MachinePlaysWhite:
6873         /* User is moving for Black */
6874         if (WhiteOnMove(currentMove)) {
6875             DisplayMoveError(_("It is White's turn"));
6876             return;
6877         }
6878         break;
6879
6880       case MachinePlaysBlack:
6881         /* User is moving for White */
6882         if (!WhiteOnMove(currentMove)) {
6883             DisplayMoveError(_("It is Black's turn"));
6884             return;
6885         }
6886         break;
6887
6888       case PlayFromGameFile:
6889             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6890       case EditGame:
6891       case IcsExamining:
6892       case BeginningOfGame:
6893       case AnalyzeMode:
6894       case Training:
6895         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6896         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6897             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6898             /* User is moving for Black */
6899             if (WhiteOnMove(currentMove)) {
6900                 DisplayMoveError(_("It is White's turn"));
6901                 return;
6902             }
6903         } else {
6904             /* User is moving for White */
6905             if (!WhiteOnMove(currentMove)) {
6906                 DisplayMoveError(_("It is Black's turn"));
6907                 return;
6908             }
6909         }
6910         break;
6911
6912       case IcsPlayingBlack:
6913         /* User is moving for Black */
6914         if (WhiteOnMove(currentMove)) {
6915             if (!appData.premove) {
6916                 DisplayMoveError(_("It is White's turn"));
6917             } else if (toX >= 0 && toY >= 0) {
6918                 premoveToX = toX;
6919                 premoveToY = toY;
6920                 premoveFromX = fromX;
6921                 premoveFromY = fromY;
6922                 premovePromoChar = promoChar;
6923                 gotPremove = 1;
6924                 if (appData.debugMode)
6925                     fprintf(debugFP, "Got premove: fromX %d,"
6926                             "fromY %d, toX %d, toY %d\n",
6927                             fromX, fromY, toX, toY);
6928             }
6929             return;
6930         }
6931         break;
6932
6933       case IcsPlayingWhite:
6934         /* User is moving for White */
6935         if (!WhiteOnMove(currentMove)) {
6936             if (!appData.premove) {
6937                 DisplayMoveError(_("It is Black's turn"));
6938             } else if (toX >= 0 && toY >= 0) {
6939                 premoveToX = toX;
6940                 premoveToY = toY;
6941                 premoveFromX = fromX;
6942                 premoveFromY = fromY;
6943                 premovePromoChar = promoChar;
6944                 gotPremove = 1;
6945                 if (appData.debugMode)
6946                     fprintf(debugFP, "Got premove: fromX %d,"
6947                             "fromY %d, toX %d, toY %d\n",
6948                             fromX, fromY, toX, toY);
6949             }
6950             return;
6951         }
6952         break;
6953
6954       default:
6955         break;
6956
6957       case EditPosition:
6958         /* EditPosition, empty square, or different color piece;
6959            click-click move is possible */
6960         if (toX == -2 || toY == -2) {
6961             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
6962             DrawPosition(FALSE, boards[currentMove]);
6963             return;
6964         } else if (toX >= 0 && toY >= 0) {
6965             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
6966                 ChessSquare q, p = boards[0][rf][ff];
6967                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
6968                 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
6969                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
6970                 if(PieceToChar(q) == '+') gatingPiece = p;
6971             }
6972             boards[0][toY][toX] = boards[0][fromY][fromX];
6973             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6974                 if(boards[0][fromY][0] != EmptySquare) {
6975                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6976                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6977                 }
6978             } else
6979             if(fromX == BOARD_RGHT+1) {
6980                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6981                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6982                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6983                 }
6984             } else
6985             boards[0][fromY][fromX] = gatingPiece;
6986             DrawPosition(FALSE, boards[currentMove]);
6987             return;
6988         }
6989         return;
6990     }
6991
6992     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
6993     pup = boards[currentMove][toY][toX];
6994
6995     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6996     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6997          if( pup != EmptySquare ) return;
6998          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6999            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7000                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7001            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7002            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7003            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7004            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7005          fromY = DROP_RANK;
7006     }
7007
7008     /* [HGM] always test for legality, to get promotion info */
7009     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7010                                          fromY, fromX, toY, toX, promoChar);
7011
7012     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7013
7014     /* [HGM] but possibly ignore an IllegalMove result */
7015     if (appData.testLegality) {
7016         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7017             DisplayMoveError(_("Illegal move"));
7018             return;
7019         }
7020     }
7021
7022     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7023         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7024              ClearPremoveHighlights(); // was included
7025         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7026         return;
7027     }
7028
7029     if(addToBookFlag) { // adding moves to book
7030         char buf[MSG_SIZ], move[MSG_SIZ];
7031         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7032         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7033         AddBookMove(buf);
7034         addToBookFlag = FALSE;
7035         ClearHighlights();
7036         return;
7037     }
7038
7039     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7040 }
7041
7042 /* Common tail of UserMoveEvent and DropMenuEvent */
7043 int
7044 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7045 {
7046     char *bookHit = 0;
7047
7048     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7049         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7050         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7051         if(WhiteOnMove(currentMove)) {
7052             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7053         } else {
7054             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7055         }
7056     }
7057
7058     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7059        move type in caller when we know the move is a legal promotion */
7060     if(moveType == NormalMove && promoChar)
7061         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7062
7063     /* [HGM] <popupFix> The following if has been moved here from
7064        UserMoveEvent(). Because it seemed to belong here (why not allow
7065        piece drops in training games?), and because it can only be
7066        performed after it is known to what we promote. */
7067     if (gameMode == Training) {
7068       /* compare the move played on the board to the next move in the
7069        * game. If they match, display the move and the opponent's response.
7070        * If they don't match, display an error message.
7071        */
7072       int saveAnimate;
7073       Board testBoard;
7074       CopyBoard(testBoard, boards[currentMove]);
7075       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7076
7077       if (CompareBoards(testBoard, boards[currentMove+1])) {
7078         ForwardInner(currentMove+1);
7079
7080         /* Autoplay the opponent's response.
7081          * if appData.animate was TRUE when Training mode was entered,
7082          * the response will be animated.
7083          */
7084         saveAnimate = appData.animate;
7085         appData.animate = animateTraining;
7086         ForwardInner(currentMove+1);
7087         appData.animate = saveAnimate;
7088
7089         /* check for the end of the game */
7090         if (currentMove >= forwardMostMove) {
7091           gameMode = PlayFromGameFile;
7092           ModeHighlight();
7093           SetTrainingModeOff();
7094           DisplayInformation(_("End of game"));
7095         }
7096       } else {
7097         DisplayError(_("Incorrect move"), 0);
7098       }
7099       return 1;
7100     }
7101
7102   /* Ok, now we know that the move is good, so we can kill
7103      the previous line in Analysis Mode */
7104   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7105                                 && currentMove < forwardMostMove) {
7106     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7107     else forwardMostMove = currentMove;
7108   }
7109
7110   ClearMap();
7111
7112   /* If we need the chess program but it's dead, restart it */
7113   ResurrectChessProgram();
7114
7115   /* A user move restarts a paused game*/
7116   if (pausing)
7117     PauseEvent();
7118
7119   thinkOutput[0] = NULLCHAR;
7120
7121   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7122
7123   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7124     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7125     return 1;
7126   }
7127
7128   if (gameMode == BeginningOfGame) {
7129     if (appData.noChessProgram) {
7130       gameMode = EditGame;
7131       SetGameInfo();
7132     } else {
7133       char buf[MSG_SIZ];
7134       gameMode = MachinePlaysBlack;
7135       StartClocks();
7136       SetGameInfo();
7137       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7138       DisplayTitle(buf);
7139       if (first.sendName) {
7140         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7141         SendToProgram(buf, &first);
7142       }
7143       StartClocks();
7144     }
7145     ModeHighlight();
7146   }
7147
7148   /* Relay move to ICS or chess engine */
7149   if (appData.icsActive) {
7150     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7151         gameMode == IcsExamining) {
7152       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7153         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7154         SendToICS("draw ");
7155         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7156       }
7157       // also send plain move, in case ICS does not understand atomic claims
7158       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7159       ics_user_moved = 1;
7160     }
7161   } else {
7162     if (first.sendTime && (gameMode == BeginningOfGame ||
7163                            gameMode == MachinePlaysWhite ||
7164                            gameMode == MachinePlaysBlack)) {
7165       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7166     }
7167     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7168          // [HGM] book: if program might be playing, let it use book
7169         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7170         first.maybeThinking = TRUE;
7171     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7172         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7173         SendBoard(&first, currentMove+1);
7174         if(second.analyzing) {
7175             if(!second.useSetboard) SendToProgram("undo\n", &second);
7176             SendBoard(&second, currentMove+1);
7177         }
7178     } else {
7179         SendMoveToProgram(forwardMostMove-1, &first);
7180         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7181     }
7182     if (currentMove == cmailOldMove + 1) {
7183       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7184     }
7185   }
7186
7187   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7188
7189   switch (gameMode) {
7190   case EditGame:
7191     if(appData.testLegality)
7192     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7193     case MT_NONE:
7194     case MT_CHECK:
7195       break;
7196     case MT_CHECKMATE:
7197     case MT_STAINMATE:
7198       if (WhiteOnMove(currentMove)) {
7199         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7200       } else {
7201         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7202       }
7203       break;
7204     case MT_STALEMATE:
7205       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7206       break;
7207     }
7208     break;
7209
7210   case MachinePlaysBlack:
7211   case MachinePlaysWhite:
7212     /* disable certain menu options while machine is thinking */
7213     SetMachineThinkingEnables();
7214     break;
7215
7216   default:
7217     break;
7218   }
7219
7220   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7221   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7222
7223   if(bookHit) { // [HGM] book: simulate book reply
7224         static char bookMove[MSG_SIZ]; // a bit generous?
7225
7226         programStats.nodes = programStats.depth = programStats.time =
7227         programStats.score = programStats.got_only_move = 0;
7228         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7229
7230         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7231         strcat(bookMove, bookHit);
7232         HandleMachineMove(bookMove, &first);
7233   }
7234   return 1;
7235 }
7236
7237 void
7238 MarkByFEN(char *fen)
7239 {
7240         int r, f;
7241         if(!appData.markers || !appData.highlightDragging) return;
7242         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7243         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7244         while(*fen) {
7245             int s = 0;
7246             marker[r][f] = 0;
7247             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7248             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7249             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7250             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7251             if(*fen == 'T') marker[r][f++] = 0; else
7252             if(*fen == 'Y') marker[r][f++] = 1; else
7253             if(*fen == 'G') marker[r][f++] = 3; else
7254             if(*fen == 'B') marker[r][f++] = 4; else
7255             if(*fen == 'C') marker[r][f++] = 5; else
7256             if(*fen == 'M') marker[r][f++] = 6; else
7257             if(*fen == 'W') marker[r][f++] = 7; else
7258             if(*fen == 'D') marker[r][f++] = 8; else
7259             if(*fen == 'R') marker[r][f++] = 2; else {
7260                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7261               f += s; fen -= s>0;
7262             }
7263             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7264             if(r < 0) break;
7265             fen++;
7266         }
7267         DrawPosition(TRUE, NULL);
7268 }
7269
7270 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7271
7272 void
7273 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7274 {
7275     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7276     Markers *m = (Markers *) closure;
7277     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7278         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7279                          || kind == WhiteCapturesEnPassant
7280                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 1;
7281     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 1;
7282 }
7283
7284 static int hoverSavedValid;
7285
7286 void
7287 MarkTargetSquares (int clear)
7288 {
7289   int x, y, sum=0;
7290   if(clear) { // no reason to ever suppress clearing
7291     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7292     hoverSavedValid = 0;
7293     if(!sum) return; // nothing was cleared,no redraw needed
7294   } else {
7295     int capt = 0;
7296     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7297        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7298     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7299     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7300       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7301       if(capt)
7302       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7303     }
7304   }
7305   DrawPosition(FALSE, NULL);
7306 }
7307
7308 int
7309 Explode (Board board, int fromX, int fromY, int toX, int toY)
7310 {
7311     if(gameInfo.variant == VariantAtomic &&
7312        (board[toY][toX] != EmptySquare ||                     // capture?
7313         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7314                          board[fromY][fromX] == BlackPawn   )
7315       )) {
7316         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7317         return TRUE;
7318     }
7319     return FALSE;
7320 }
7321
7322 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7323
7324 int
7325 CanPromote (ChessSquare piece, int y)
7326 {
7327         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7328         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7329         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7330         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7331            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7332            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7333          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7334         return (piece == BlackPawn && y <= zone ||
7335                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7336                 piece == BlackLance && y <= zone ||
7337                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7338 }
7339
7340 void
7341 HoverEvent (int xPix, int yPix, int x, int y)
7342 {
7343         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7344         int r, f;
7345         if(!first.highlight) return;
7346         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7347         if(x == oldX && y == oldY) return; // only do something if we enter new square
7348         oldFromX = fromX; oldFromY = fromY;
7349         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7350           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7351             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7352           hoverSavedValid = 1;
7353         } else if(oldX != x || oldY != y) {
7354           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7355           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7356           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7357             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7358           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7359             char buf[MSG_SIZ];
7360             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7361             SendToProgram(buf, &first);
7362           }
7363           oldX = x; oldY = y;
7364 //        SetHighlights(fromX, fromY, x, y);
7365         }
7366 }
7367
7368 void ReportClick(char *action, int x, int y)
7369 {
7370         char buf[MSG_SIZ]; // Inform engine of what user does
7371         int r, f;
7372         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7373           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7374             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7375         if(!first.highlight || gameMode == EditPosition) return;
7376         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7377         SendToProgram(buf, &first);
7378 }
7379
7380 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7381
7382 void
7383 LeftClick (ClickType clickType, int xPix, int yPix)
7384 {
7385     int x, y;
7386     Boolean saveAnimate;
7387     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7388     char promoChoice = NULLCHAR;
7389     ChessSquare piece;
7390     static TimeMark lastClickTime, prevClickTime;
7391
7392     x = EventToSquare(xPix, BOARD_WIDTH);
7393     y = EventToSquare(yPix, BOARD_HEIGHT);
7394     if (!flipView && y >= 0) {
7395         y = BOARD_HEIGHT - 1 - y;
7396     }
7397     if (flipView && x >= 0) {
7398         x = BOARD_WIDTH - 1 - x;
7399     }
7400
7401     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7402         static int dummy;
7403         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7404         right = TRUE;
7405         return;
7406     }
7407
7408     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7409
7410     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7411
7412     if (clickType == Press) ErrorPopDown();
7413     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7414
7415     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7416         defaultPromoChoice = promoSweep;
7417         promoSweep = EmptySquare;   // terminate sweep
7418         promoDefaultAltered = TRUE;
7419         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7420     }
7421
7422     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7423         if(clickType == Release) return; // ignore upclick of click-click destination
7424         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7425         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7426         if(gameInfo.holdingsWidth &&
7427                 (WhiteOnMove(currentMove)
7428                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7429                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7430             // click in right holdings, for determining promotion piece
7431             ChessSquare p = boards[currentMove][y][x];
7432             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7433             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7434             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7435                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7436                 fromX = fromY = -1;
7437                 return;
7438             }
7439         }
7440         DrawPosition(FALSE, boards[currentMove]);
7441         return;
7442     }
7443
7444     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7445     if(clickType == Press
7446             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7447               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7448               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7449         return;
7450
7451     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7452         // could be static click on premove from-square: abort premove
7453         gotPremove = 0;
7454         ClearPremoveHighlights();
7455     }
7456
7457     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7458         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7459
7460     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7461         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7462                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7463         defaultPromoChoice = DefaultPromoChoice(side);
7464     }
7465
7466     autoQueen = appData.alwaysPromoteToQueen;
7467
7468     if (fromX == -1) {
7469       int originalY = y;
7470       gatingPiece = EmptySquare;
7471       if (clickType != Press) {
7472         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7473             DragPieceEnd(xPix, yPix); dragging = 0;
7474             DrawPosition(FALSE, NULL);
7475         }
7476         return;
7477       }
7478       doubleClick = FALSE;
7479       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7480         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7481       }
7482       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7483       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7484          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7485          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7486             /* First square */
7487             if (OKToStartUserMove(fromX, fromY)) {
7488                 second = 0;
7489                 ReportClick("lift", x, y);
7490                 MarkTargetSquares(0);
7491                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7492                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7493                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7494                     promoSweep = defaultPromoChoice;
7495                     selectFlag = 0; lastX = xPix; lastY = yPix;
7496                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7497                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7498                 }
7499                 if (appData.highlightDragging) {
7500                     SetHighlights(fromX, fromY, -1, -1);
7501                 } else {
7502                     ClearHighlights();
7503                 }
7504             } else fromX = fromY = -1;
7505             return;
7506         }
7507     }
7508 printf("to click %d,%d\n",x,y);
7509     /* fromX != -1 */
7510     if (clickType == Press && gameMode != EditPosition) {
7511         ChessSquare fromP;
7512         ChessSquare toP;
7513         int frc;
7514
7515         // ignore off-board to clicks
7516         if(y < 0 || x < 0) return;
7517
7518         /* Check if clicking again on the same color piece */
7519         fromP = boards[currentMove][fromY][fromX];
7520         toP = boards[currentMove][y][x];
7521         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7522         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7523             legal[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7524            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7525              WhitePawn <= toP && toP <= WhiteKing &&
7526              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7527              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7528             (BlackPawn <= fromP && fromP <= BlackKing &&
7529              BlackPawn <= toP && toP <= BlackKing &&
7530              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7531              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7532             /* Clicked again on same color piece -- changed his mind */
7533             second = (x == fromX && y == fromY);
7534             killX = killY = -1;
7535             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7536                 second = FALSE; // first double-click rather than scond click
7537                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7538             }
7539             promoDefaultAltered = FALSE;
7540             MarkTargetSquares(1);
7541            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7542             if (appData.highlightDragging) {
7543                 SetHighlights(x, y, -1, -1);
7544             } else {
7545                 ClearHighlights();
7546             }
7547             if (OKToStartUserMove(x, y)) {
7548                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7549                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7550                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7551                  gatingPiece = boards[currentMove][fromY][fromX];
7552                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7553                 fromX = x;
7554                 fromY = y; dragging = 1;
7555                 ReportClick("lift", x, y);
7556                 MarkTargetSquares(0);
7557                 DragPieceBegin(xPix, yPix, FALSE);
7558                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7559                     promoSweep = defaultPromoChoice;
7560                     selectFlag = 0; lastX = xPix; lastY = yPix;
7561                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7562                 }
7563             }
7564            }
7565            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7566            second = FALSE;
7567         }
7568         // ignore clicks on holdings
7569         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7570     }
7571 printf("A type=%d\n",clickType);
7572
7573     if(x == fromX && y == fromY && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7574         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7575         return;
7576     }
7577
7578     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7579         DragPieceEnd(xPix, yPix); dragging = 0;
7580         if(clearFlag) {
7581             // a deferred attempt to click-click move an empty square on top of a piece
7582             boards[currentMove][y][x] = EmptySquare;
7583             ClearHighlights();
7584             DrawPosition(FALSE, boards[currentMove]);
7585             fromX = fromY = -1; clearFlag = 0;
7586             return;
7587         }
7588         if (appData.animateDragging) {
7589             /* Undo animation damage if any */
7590             DrawPosition(FALSE, NULL);
7591         }
7592         if (second || sweepSelecting) {
7593             /* Second up/down in same square; just abort move */
7594             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7595             second = sweepSelecting = 0;
7596             fromX = fromY = -1;
7597             gatingPiece = EmptySquare;
7598             MarkTargetSquares(1);
7599             ClearHighlights();
7600             gotPremove = 0;
7601             ClearPremoveHighlights();
7602         } else {
7603             /* First upclick in same square; start click-click mode */
7604             SetHighlights(x, y, -1, -1);
7605         }
7606         return;
7607     }
7608
7609     clearFlag = 0;
7610 printf("B\n");
7611     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7612        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7613         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7614         DisplayMessage(_("only marked squares are legal"),"");
7615         DrawPosition(TRUE, NULL);
7616         return; // ignore to-click
7617     }
7618 printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
7619     /* we now have a different from- and (possibly off-board) to-square */
7620     /* Completed move */
7621     if(!sweepSelecting) {
7622         toX = x;
7623         toY = y;
7624     }
7625
7626     piece = boards[currentMove][fromY][fromX];
7627
7628     saveAnimate = appData.animate;
7629     if (clickType == Press) {
7630         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7631         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7632             // must be Edit Position mode with empty-square selected
7633             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7634             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7635             return;
7636         }
7637         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7638             return;
7639         }
7640         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7641             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7642         } else
7643         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7644         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7645           if(appData.sweepSelect) {
7646             promoSweep = defaultPromoChoice;
7647             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7648             selectFlag = 0; lastX = xPix; lastY = yPix;
7649             Sweep(0); // Pawn that is going to promote: preview promotion piece
7650             sweepSelecting = 1;
7651             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7652             MarkTargetSquares(1);
7653           }
7654           return; // promo popup appears on up-click
7655         }
7656         /* Finish clickclick move */
7657         if (appData.animate || appData.highlightLastMove) {
7658             SetHighlights(fromX, fromY, toX, toY);
7659         } else {
7660             ClearHighlights();
7661         }
7662     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7663         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7664         if (appData.animate || appData.highlightLastMove) {
7665             SetHighlights(fromX, fromY, toX, toY);
7666         } else {
7667             ClearHighlights();
7668         }
7669     } else {
7670 #if 0
7671 // [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
7672         /* Finish drag move */
7673         if (appData.highlightLastMove) {
7674             SetHighlights(fromX, fromY, toX, toY);
7675         } else {
7676             ClearHighlights();
7677         }
7678 #endif
7679         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7680         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7681           dragging *= 2;            // flag button-less dragging if we are dragging
7682           MarkTargetSquares(1);
7683           if(x == killX && y == killY) killX = killY = -1; else {
7684             killX = x; killY = y;     //remeber this square as intermediate
7685             ReportClick("put", x, y); // and inform engine
7686             ReportClick("lift", x, y);
7687             MarkTargetSquares(0);
7688             return;
7689           }
7690         }
7691         DragPieceEnd(xPix, yPix); dragging = 0;
7692         /* Don't animate move and drag both */
7693         appData.animate = FALSE;
7694     }
7695
7696     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7697     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7698         ChessSquare piece = boards[currentMove][fromY][fromX];
7699         if(gameMode == EditPosition && piece != EmptySquare &&
7700            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7701             int n;
7702
7703             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7704                 n = PieceToNumber(piece - (int)BlackPawn);
7705                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7706                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7707                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7708             } else
7709             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7710                 n = PieceToNumber(piece);
7711                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7712                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7713                 boards[currentMove][n][BOARD_WIDTH-2]++;
7714             }
7715             boards[currentMove][fromY][fromX] = EmptySquare;
7716         }
7717         ClearHighlights();
7718         fromX = fromY = -1;
7719         MarkTargetSquares(1);
7720         DrawPosition(TRUE, boards[currentMove]);
7721         return;
7722     }
7723
7724     // off-board moves should not be highlighted
7725     if(x < 0 || y < 0) ClearHighlights();
7726     else ReportClick("put", x, y);
7727
7728     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7729
7730     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7731         SetHighlights(fromX, fromY, toX, toY);
7732         MarkTargetSquares(1);
7733         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7734             // [HGM] super: promotion to captured piece selected from holdings
7735             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7736             promotionChoice = TRUE;
7737             // kludge follows to temporarily execute move on display, without promoting yet
7738             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7739             boards[currentMove][toY][toX] = p;
7740             DrawPosition(FALSE, boards[currentMove]);
7741             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7742             boards[currentMove][toY][toX] = q;
7743             DisplayMessage("Click in holdings to choose piece", "");
7744             return;
7745         }
7746         PromotionPopUp(promoChoice);
7747     } else {
7748         int oldMove = currentMove;
7749         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7750         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7751         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7752         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7753            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7754             DrawPosition(TRUE, boards[currentMove]);
7755         MarkTargetSquares(1);
7756         fromX = fromY = -1;
7757     }
7758     appData.animate = saveAnimate;
7759     if (appData.animate || appData.animateDragging) {
7760         /* Undo animation damage if needed */
7761         DrawPosition(FALSE, NULL);
7762     }
7763 }
7764
7765 int
7766 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7767 {   // front-end-free part taken out of PieceMenuPopup
7768     int whichMenu; int xSqr, ySqr;
7769
7770     if(seekGraphUp) { // [HGM] seekgraph
7771         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7772         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7773         return -2;
7774     }
7775
7776     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7777          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7778         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7779         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7780         if(action == Press)   {
7781             originalFlip = flipView;
7782             flipView = !flipView; // temporarily flip board to see game from partners perspective
7783             DrawPosition(TRUE, partnerBoard);
7784             DisplayMessage(partnerStatus, "");
7785             partnerUp = TRUE;
7786         } else if(action == Release) {
7787             flipView = originalFlip;
7788             DrawPosition(TRUE, boards[currentMove]);
7789             partnerUp = FALSE;
7790         }
7791         return -2;
7792     }
7793
7794     xSqr = EventToSquare(x, BOARD_WIDTH);
7795     ySqr = EventToSquare(y, BOARD_HEIGHT);
7796     if (action == Release) {
7797         if(pieceSweep != EmptySquare) {
7798             EditPositionMenuEvent(pieceSweep, toX, toY);
7799             pieceSweep = EmptySquare;
7800         } else UnLoadPV(); // [HGM] pv
7801     }
7802     if (action != Press) return -2; // return code to be ignored
7803     switch (gameMode) {
7804       case IcsExamining:
7805         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7806       case EditPosition:
7807         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7808         if (xSqr < 0 || ySqr < 0) return -1;
7809         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7810         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7811         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7812         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7813         NextPiece(0);
7814         return 2; // grab
7815       case IcsObserving:
7816         if(!appData.icsEngineAnalyze) return -1;
7817       case IcsPlayingWhite:
7818       case IcsPlayingBlack:
7819         if(!appData.zippyPlay) goto noZip;
7820       case AnalyzeMode:
7821       case AnalyzeFile:
7822       case MachinePlaysWhite:
7823       case MachinePlaysBlack:
7824       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7825         if (!appData.dropMenu) {
7826           LoadPV(x, y);
7827           return 2; // flag front-end to grab mouse events
7828         }
7829         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7830            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7831       case EditGame:
7832       noZip:
7833         if (xSqr < 0 || ySqr < 0) return -1;
7834         if (!appData.dropMenu || appData.testLegality &&
7835             gameInfo.variant != VariantBughouse &&
7836             gameInfo.variant != VariantCrazyhouse) return -1;
7837         whichMenu = 1; // drop menu
7838         break;
7839       default:
7840         return -1;
7841     }
7842
7843     if (((*fromX = xSqr) < 0) ||
7844         ((*fromY = ySqr) < 0)) {
7845         *fromX = *fromY = -1;
7846         return -1;
7847     }
7848     if (flipView)
7849       *fromX = BOARD_WIDTH - 1 - *fromX;
7850     else
7851       *fromY = BOARD_HEIGHT - 1 - *fromY;
7852
7853     return whichMenu;
7854 }
7855
7856 void
7857 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7858 {
7859 //    char * hint = lastHint;
7860     FrontEndProgramStats stats;
7861
7862     stats.which = cps == &first ? 0 : 1;
7863     stats.depth = cpstats->depth;
7864     stats.nodes = cpstats->nodes;
7865     stats.score = cpstats->score;
7866     stats.time = cpstats->time;
7867     stats.pv = cpstats->movelist;
7868     stats.hint = lastHint;
7869     stats.an_move_index = 0;
7870     stats.an_move_count = 0;
7871
7872     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7873         stats.hint = cpstats->move_name;
7874         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7875         stats.an_move_count = cpstats->nr_moves;
7876     }
7877
7878     if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
7879
7880     SetProgramStats( &stats );
7881 }
7882
7883 void
7884 ClearEngineOutputPane (int which)
7885 {
7886     static FrontEndProgramStats dummyStats;
7887     dummyStats.which = which;
7888     dummyStats.pv = "#";
7889     SetProgramStats( &dummyStats );
7890 }
7891
7892 #define MAXPLAYERS 500
7893
7894 char *
7895 TourneyStandings (int display)
7896 {
7897     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7898     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7899     char result, *p, *names[MAXPLAYERS];
7900
7901     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7902         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7903     names[0] = p = strdup(appData.participants);
7904     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7905
7906     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7907
7908     while(result = appData.results[nr]) {
7909         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7910         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7911         wScore = bScore = 0;
7912         switch(result) {
7913           case '+': wScore = 2; break;
7914           case '-': bScore = 2; break;
7915           case '=': wScore = bScore = 1; break;
7916           case ' ':
7917           case '*': return strdup("busy"); // tourney not finished
7918         }
7919         score[w] += wScore;
7920         score[b] += bScore;
7921         games[w]++;
7922         games[b]++;
7923         nr++;
7924     }
7925     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7926     for(w=0; w<nPlayers; w++) {
7927         bScore = -1;
7928         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7929         ranking[w] = b; points[w] = bScore; score[b] = -2;
7930     }
7931     p = malloc(nPlayers*34+1);
7932     for(w=0; w<nPlayers && w<display; w++)
7933         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7934     free(names[0]);
7935     return p;
7936 }
7937
7938 void
7939 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7940 {       // count all piece types
7941         int p, f, r;
7942         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7943         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7944         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7945                 p = board[r][f];
7946                 pCnt[p]++;
7947                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7948                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7949                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7950                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7951                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7952                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7953         }
7954 }
7955
7956 int
7957 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7958 {
7959         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7960         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7961
7962         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7963         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7964         if(myPawns == 2 && nMine == 3) // KPP
7965             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7966         if(myPawns == 1 && nMine == 2) // KP
7967             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7968         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7969             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7970         if(myPawns) return FALSE;
7971         if(pCnt[WhiteRook+side])
7972             return pCnt[BlackRook-side] ||
7973                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7974                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7975                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7976         if(pCnt[WhiteCannon+side]) {
7977             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7978             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7979         }
7980         if(pCnt[WhiteKnight+side])
7981             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7982         return FALSE;
7983 }
7984
7985 int
7986 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7987 {
7988         VariantClass v = gameInfo.variant;
7989
7990         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7991         if(v == VariantShatranj) return TRUE; // always winnable through baring
7992         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7993         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7994
7995         if(v == VariantXiangqi) {
7996                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7997
7998                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7999                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8000                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8001                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8002                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8003                 if(stale) // we have at least one last-rank P plus perhaps C
8004                     return majors // KPKX
8005                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8006                 else // KCA*E*
8007                     return pCnt[WhiteFerz+side] // KCAK
8008                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8009                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8010                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8011
8012         } else if(v == VariantKnightmate) {
8013                 if(nMine == 1) return FALSE;
8014                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8015         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8016                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8017
8018                 if(nMine == 1) return FALSE; // bare King
8019                 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
8020                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8021                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8022                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8023                 if(pCnt[WhiteKnight+side])
8024                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8025                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8026                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8027                 if(nBishops)
8028                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8029                 if(pCnt[WhiteAlfil+side])
8030                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8031                 if(pCnt[WhiteWazir+side])
8032                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8033         }
8034
8035         return TRUE;
8036 }
8037
8038 int
8039 CompareWithRights (Board b1, Board b2)
8040 {
8041     int rights = 0;
8042     if(!CompareBoards(b1, b2)) return FALSE;
8043     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8044     /* compare castling rights */
8045     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8046            rights++; /* King lost rights, while rook still had them */
8047     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8048         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8049            rights++; /* but at least one rook lost them */
8050     }
8051     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8052            rights++;
8053     if( b1[CASTLING][5] != NoRights ) {
8054         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8055            rights++;
8056     }
8057     return rights == 0;
8058 }
8059
8060 int
8061 Adjudicate (ChessProgramState *cps)
8062 {       // [HGM] some adjudications useful with buggy engines
8063         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8064         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8065         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8066         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8067         int k, drop, count = 0; static int bare = 1;
8068         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8069         Boolean canAdjudicate = !appData.icsActive;
8070
8071         // most tests only when we understand the game, i.e. legality-checking on
8072             if( appData.testLegality )
8073             {   /* [HGM] Some more adjudications for obstinate engines */
8074                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8075                 static int moveCount = 6;
8076                 ChessMove result;
8077                 char *reason = NULL;
8078
8079                 /* Count what is on board. */
8080                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8081
8082                 /* Some material-based adjudications that have to be made before stalemate test */
8083                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8084                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8085                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8086                      if(canAdjudicate && appData.checkMates) {
8087                          if(engineOpponent)
8088                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8089                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8090                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8091                          return 1;
8092                      }
8093                 }
8094
8095                 /* Bare King in Shatranj (loses) or Losers (wins) */
8096                 if( nrW == 1 || nrB == 1) {
8097                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8098                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8099                      if(canAdjudicate && appData.checkMates) {
8100                          if(engineOpponent)
8101                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8102                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8103                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8104                          return 1;
8105                      }
8106                   } else
8107                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8108                   {    /* bare King */
8109                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8110                         if(canAdjudicate && appData.checkMates) {
8111                             /* but only adjudicate if adjudication enabled */
8112                             if(engineOpponent)
8113                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8114                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8115                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8116                             return 1;
8117                         }
8118                   }
8119                 } else bare = 1;
8120
8121
8122             // don't wait for engine to announce game end if we can judge ourselves
8123             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8124               case MT_CHECK:
8125                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8126                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8127                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8128                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8129                             checkCnt++;
8130                         if(checkCnt >= 2) {
8131                             reason = "Xboard adjudication: 3rd check";
8132                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8133                             break;
8134                         }
8135                     }
8136                 }
8137               case MT_NONE:
8138               default:
8139                 break;
8140               case MT_STEALMATE:
8141               case MT_STALEMATE:
8142               case MT_STAINMATE:
8143                 reason = "Xboard adjudication: Stalemate";
8144                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8145                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8146                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8147                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8148                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8149                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8150                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8151                                                                         EP_CHECKMATE : EP_WINS);
8152                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8153                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8154                 }
8155                 break;
8156               case MT_CHECKMATE:
8157                 reason = "Xboard adjudication: Checkmate";
8158                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8159                 if(gameInfo.variant == VariantShogi) {
8160                     if(forwardMostMove > backwardMostMove
8161                        && moveList[forwardMostMove-1][1] == '@'
8162                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8163                         reason = "XBoard adjudication: pawn-drop mate";
8164                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8165                     }
8166                 }
8167                 break;
8168             }
8169
8170                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8171                     case EP_STALEMATE:
8172                         result = GameIsDrawn; break;
8173                     case EP_CHECKMATE:
8174                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8175                     case EP_WINS:
8176                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8177                     default:
8178                         result = EndOfFile;
8179                 }
8180                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8181                     if(engineOpponent)
8182                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8183                     GameEnds( result, reason, GE_XBOARD );
8184                     return 1;
8185                 }
8186
8187                 /* Next absolutely insufficient mating material. */
8188                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8189                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8190                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8191
8192                      /* always flag draws, for judging claims */
8193                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8194
8195                      if(canAdjudicate && appData.materialDraws) {
8196                          /* but only adjudicate them if adjudication enabled */
8197                          if(engineOpponent) {
8198                            SendToProgram("force\n", engineOpponent); // suppress reply
8199                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8200                          }
8201                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8202                          return 1;
8203                      }
8204                 }
8205
8206                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8207                 if(gameInfo.variant == VariantXiangqi ?
8208                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8209                  : nrW + nrB == 4 &&
8210                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8211                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8212                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8213                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8214                    ) ) {
8215                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8216                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8217                           if(engineOpponent) {
8218                             SendToProgram("force\n", engineOpponent); // suppress reply
8219                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8220                           }
8221                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8222                           return 1;
8223                      }
8224                 } else moveCount = 6;
8225             }
8226
8227         // Repetition draws and 50-move rule can be applied independently of legality testing
8228
8229                 /* Check for rep-draws */
8230                 count = 0;
8231                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8232                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8233                 for(k = forwardMostMove-2;
8234                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8235                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8236                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8237                     k-=2)
8238                 {   int rights=0;
8239                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8240                         /* compare castling rights */
8241                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8242                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8243                                 rights++; /* King lost rights, while rook still had them */
8244                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8245                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8246                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8247                                    rights++; /* but at least one rook lost them */
8248                         }
8249                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8250                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8251                                 rights++;
8252                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8253                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8254                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8255                                    rights++;
8256                         }
8257                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8258                             && appData.drawRepeats > 1) {
8259                              /* adjudicate after user-specified nr of repeats */
8260                              int result = GameIsDrawn;
8261                              char *details = "XBoard adjudication: repetition draw";
8262                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8263                                 // [HGM] xiangqi: check for forbidden perpetuals
8264                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8265                                 for(m=forwardMostMove; m>k; m-=2) {
8266                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8267                                         ourPerpetual = 0; // the current mover did not always check
8268                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8269                                         hisPerpetual = 0; // the opponent did not always check
8270                                 }
8271                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8272                                                                         ourPerpetual, hisPerpetual);
8273                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8274                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8275                                     details = "Xboard adjudication: perpetual checking";
8276                                 } else
8277                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8278                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8279                                 } else
8280                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8281                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8282                                         result = BlackWins;
8283                                         details = "Xboard adjudication: repetition";
8284                                     }
8285                                 } else // it must be XQ
8286                                 // Now check for perpetual chases
8287                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8288                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8289                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8290                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8291                                         static char resdet[MSG_SIZ];
8292                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8293                                         details = resdet;
8294                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8295                                     } else
8296                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8297                                         break; // Abort repetition-checking loop.
8298                                 }
8299                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8300                              }
8301                              if(engineOpponent) {
8302                                SendToProgram("force\n", engineOpponent); // suppress reply
8303                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8304                              }
8305                              GameEnds( result, details, GE_XBOARD );
8306                              return 1;
8307                         }
8308                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8309                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8310                     }
8311                 }
8312
8313                 /* Now we test for 50-move draws. Determine ply count */
8314                 count = forwardMostMove;
8315                 /* look for last irreversble move */
8316                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8317                     count--;
8318                 /* if we hit starting position, add initial plies */
8319                 if( count == backwardMostMove )
8320                     count -= initialRulePlies;
8321                 count = forwardMostMove - count;
8322                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8323                         // adjust reversible move counter for checks in Xiangqi
8324                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8325                         if(i < backwardMostMove) i = backwardMostMove;
8326                         while(i <= forwardMostMove) {
8327                                 lastCheck = inCheck; // check evasion does not count
8328                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8329                                 if(inCheck || lastCheck) count--; // check does not count
8330                                 i++;
8331                         }
8332                 }
8333                 if( count >= 100)
8334                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8335                          /* this is used to judge if draw claims are legal */
8336                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8337                          if(engineOpponent) {
8338                            SendToProgram("force\n", engineOpponent); // suppress reply
8339                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8340                          }
8341                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8342                          return 1;
8343                 }
8344
8345                 /* if draw offer is pending, treat it as a draw claim
8346                  * when draw condition present, to allow engines a way to
8347                  * claim draws before making their move to avoid a race
8348                  * condition occurring after their move
8349                  */
8350                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8351                          char *p = NULL;
8352                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8353                              p = "Draw claim: 50-move rule";
8354                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8355                              p = "Draw claim: 3-fold repetition";
8356                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8357                              p = "Draw claim: insufficient mating material";
8358                          if( p != NULL && canAdjudicate) {
8359                              if(engineOpponent) {
8360                                SendToProgram("force\n", engineOpponent); // suppress reply
8361                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8362                              }
8363                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8364                              return 1;
8365                          }
8366                 }
8367
8368                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8369                     if(engineOpponent) {
8370                       SendToProgram("force\n", engineOpponent); // suppress reply
8371                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8372                     }
8373                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8374                     return 1;
8375                 }
8376         return 0;
8377 }
8378
8379 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8380 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8381 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8382
8383 static int
8384 BitbaseProbe ()
8385 {
8386     int pieces[10], squares[10], cnt=0, r, f, res;
8387     static int loaded;
8388     static PPROBE_EGBB probeBB;
8389     if(!appData.testLegality) return 10;
8390     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8391     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8392     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8393     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8394         ChessSquare piece = boards[forwardMostMove][r][f];
8395         int black = (piece >= BlackPawn);
8396         int type = piece - black*BlackPawn;
8397         if(piece == EmptySquare) continue;
8398         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8399         if(type == WhiteKing) type = WhiteQueen + 1;
8400         type = egbbCode[type];
8401         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8402         pieces[cnt] = type + black*6;
8403         if(++cnt > 5) return 11;
8404     }
8405     pieces[cnt] = squares[cnt] = 0;
8406     // probe EGBB
8407     if(loaded == 2) return 13; // loading failed before
8408     if(loaded == 0) {
8409         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8410         HMODULE lib;
8411         PLOAD_EGBB loadBB;
8412         loaded = 2; // prepare for failure
8413         if(!path) return 13; // no egbb installed
8414         strncpy(buf, path + 8, MSG_SIZ);
8415         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8416         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8417         lib = LoadLibrary(buf);
8418         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8419         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8420         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8421         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8422         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8423         loaded = 1; // success!
8424     }
8425     res = probeBB(forwardMostMove & 1, pieces, squares);
8426     return res > 0 ? 1 : res < 0 ? -1 : 0;
8427 }
8428
8429 char *
8430 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8431 {   // [HGM] book: this routine intercepts moves to simulate book replies
8432     char *bookHit = NULL;
8433
8434     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8435         char buf[MSG_SIZ];
8436         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8437         SendToProgram(buf, cps);
8438     }
8439     //first determine if the incoming move brings opponent into his book
8440     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8441         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8442     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8443     if(bookHit != NULL && !cps->bookSuspend) {
8444         // make sure opponent is not going to reply after receiving move to book position
8445         SendToProgram("force\n", cps);
8446         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8447     }
8448     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8449     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8450     // now arrange restart after book miss
8451     if(bookHit) {
8452         // after a book hit we never send 'go', and the code after the call to this routine
8453         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8454         char buf[MSG_SIZ], *move = bookHit;
8455         if(cps->useSAN) {
8456             int fromX, fromY, toX, toY;
8457             char promoChar;
8458             ChessMove moveType;
8459             move = buf + 30;
8460             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8461                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8462                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8463                                     PosFlags(forwardMostMove),
8464                                     fromY, fromX, toY, toX, promoChar, move);
8465             } else {
8466                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8467                 bookHit = NULL;
8468             }
8469         }
8470         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8471         SendToProgram(buf, cps);
8472         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8473     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8474         SendToProgram("go\n", cps);
8475         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8476     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8477         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8478             SendToProgram("go\n", cps);
8479         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8480     }
8481     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8482 }
8483
8484 int
8485 LoadError (char *errmess, ChessProgramState *cps)
8486 {   // unloads engine and switches back to -ncp mode if it was first
8487     if(cps->initDone) return FALSE;
8488     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8489     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8490     cps->pr = NoProc;
8491     if(cps == &first) {
8492         appData.noChessProgram = TRUE;
8493         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8494         gameMode = BeginningOfGame; ModeHighlight();
8495         SetNCPMode();
8496     }
8497     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8498     DisplayMessage("", ""); // erase waiting message
8499     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8500     return TRUE;
8501 }
8502
8503 char *savedMessage;
8504 ChessProgramState *savedState;
8505 void
8506 DeferredBookMove (void)
8507 {
8508         if(savedState->lastPing != savedState->lastPong)
8509                     ScheduleDelayedEvent(DeferredBookMove, 10);
8510         else
8511         HandleMachineMove(savedMessage, savedState);
8512 }
8513
8514 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8515 static ChessProgramState *stalledEngine;
8516 static char stashedInputMove[MSG_SIZ];
8517
8518 void
8519 HandleMachineMove (char *message, ChessProgramState *cps)
8520 {
8521     static char firstLeg[20];
8522     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8523     char realname[MSG_SIZ];
8524     int fromX, fromY, toX, toY;
8525     ChessMove moveType;
8526     char promoChar, roar;
8527     char *p, *pv=buf1;
8528     int machineWhite, oldError;
8529     char *bookHit;
8530
8531     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8532         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8533         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8534             DisplayError(_("Invalid pairing from pairing engine"), 0);
8535             return;
8536         }
8537         pairingReceived = 1;
8538         NextMatchGame();
8539         return; // Skim the pairing messages here.
8540     }
8541
8542     oldError = cps->userError; cps->userError = 0;
8543
8544 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8545     /*
8546      * Kludge to ignore BEL characters
8547      */
8548     while (*message == '\007') message++;
8549
8550     /*
8551      * [HGM] engine debug message: ignore lines starting with '#' character
8552      */
8553     if(cps->debug && *message == '#') return;
8554
8555     /*
8556      * Look for book output
8557      */
8558     if (cps == &first && bookRequested) {
8559         if (message[0] == '\t' || message[0] == ' ') {
8560             /* Part of the book output is here; append it */
8561             strcat(bookOutput, message);
8562             strcat(bookOutput, "  \n");
8563             return;
8564         } else if (bookOutput[0] != NULLCHAR) {
8565             /* All of book output has arrived; display it */
8566             char *p = bookOutput;
8567             while (*p != NULLCHAR) {
8568                 if (*p == '\t') *p = ' ';
8569                 p++;
8570             }
8571             DisplayInformation(bookOutput);
8572             bookRequested = FALSE;
8573             /* Fall through to parse the current output */
8574         }
8575     }
8576
8577     /*
8578      * Look for machine move.
8579      */
8580     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8581         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8582     {
8583         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8584             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8585             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8586             stalledEngine = cps;
8587             if(appData.ponderNextMove) { // bring opponent out of ponder
8588                 if(gameMode == TwoMachinesPlay) {
8589                     if(cps->other->pause)
8590                         PauseEngine(cps->other);
8591                     else
8592                         SendToProgram("easy\n", cps->other);
8593                 }
8594             }
8595             StopClocks();
8596             return;
8597         }
8598
8599         /* This method is only useful on engines that support ping */
8600         if (cps->lastPing != cps->lastPong) {
8601           if (gameMode == BeginningOfGame) {
8602             /* Extra move from before last new; ignore */
8603             if (appData.debugMode) {
8604                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8605             }
8606           } else {
8607             if (appData.debugMode) {
8608                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8609                         cps->which, gameMode);
8610             }
8611
8612             SendToProgram("undo\n", cps);
8613           }
8614           return;
8615         }
8616
8617         switch (gameMode) {
8618           case BeginningOfGame:
8619             /* Extra move from before last reset; ignore */
8620             if (appData.debugMode) {
8621                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8622             }
8623             return;
8624
8625           case EndOfGame:
8626           case IcsIdle:
8627           default:
8628             /* Extra move after we tried to stop.  The mode test is
8629                not a reliable way of detecting this problem, but it's
8630                the best we can do on engines that don't support ping.
8631             */
8632             if (appData.debugMode) {
8633                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8634                         cps->which, gameMode);
8635             }
8636             SendToProgram("undo\n", cps);
8637             return;
8638
8639           case MachinePlaysWhite:
8640           case IcsPlayingWhite:
8641             machineWhite = TRUE;
8642             break;
8643
8644           case MachinePlaysBlack:
8645           case IcsPlayingBlack:
8646             machineWhite = FALSE;
8647             break;
8648
8649           case TwoMachinesPlay:
8650             machineWhite = (cps->twoMachinesColor[0] == 'w');
8651             break;
8652         }
8653         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8654             if (appData.debugMode) {
8655                 fprintf(debugFP,
8656                         "Ignoring move out of turn by %s, gameMode %d"
8657                         ", forwardMost %d\n",
8658                         cps->which, gameMode, forwardMostMove);
8659             }
8660             return;
8661         }
8662
8663         if(cps->alphaRank) AlphaRank(machineMove, 4);
8664
8665         // [HGM] lion: (some very limited) support for Alien protocol
8666         killX = killY = -1;
8667         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8668             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8669             return;
8670         } else if(firstLeg[0]) { // there was a previous leg;
8671             // only support case where same piece makes two step (and don't even test that!)
8672             char buf[20], *p = machineMove+1, *q = buf+1, f;
8673             safeStrCpy(buf, machineMove, 20);
8674             while(isdigit(*q)) q++; // find start of to-square
8675             safeStrCpy(machineMove, firstLeg, 20);
8676             while(isdigit(*p)) p++;
8677             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8678             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8679             firstLeg[0] = NULLCHAR;
8680         }
8681
8682         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8683                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8684             /* Machine move could not be parsed; ignore it. */
8685           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8686                     machineMove, _(cps->which));
8687             DisplayMoveError(buf1);
8688             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8689                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8690             if (gameMode == TwoMachinesPlay) {
8691               GameEnds(machineWhite ? BlackWins : WhiteWins,
8692                        buf1, GE_XBOARD);
8693             }
8694             return;
8695         }
8696
8697         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8698         /* So we have to redo legality test with true e.p. status here,  */
8699         /* to make sure an illegal e.p. capture does not slip through,   */
8700         /* to cause a forfeit on a justified illegal-move complaint      */
8701         /* of the opponent.                                              */
8702         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8703            ChessMove moveType;
8704            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8705                              fromY, fromX, toY, toX, promoChar);
8706             if(moveType == IllegalMove) {
8707               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8708                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8709                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8710                            buf1, GE_XBOARD);
8711                 return;
8712            } else if(!appData.fischerCastling)
8713            /* [HGM] Kludge to handle engines that send FRC-style castling
8714               when they shouldn't (like TSCP-Gothic) */
8715            switch(moveType) {
8716              case WhiteASideCastleFR:
8717              case BlackASideCastleFR:
8718                toX+=2;
8719                currentMoveString[2]++;
8720                break;
8721              case WhiteHSideCastleFR:
8722              case BlackHSideCastleFR:
8723                toX--;
8724                currentMoveString[2]--;
8725                break;
8726              default: ; // nothing to do, but suppresses warning of pedantic compilers
8727            }
8728         }
8729         hintRequested = FALSE;
8730         lastHint[0] = NULLCHAR;
8731         bookRequested = FALSE;
8732         /* Program may be pondering now */
8733         cps->maybeThinking = TRUE;
8734         if (cps->sendTime == 2) cps->sendTime = 1;
8735         if (cps->offeredDraw) cps->offeredDraw--;
8736
8737         /* [AS] Save move info*/
8738         pvInfoList[ forwardMostMove ].score = programStats.score;
8739         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8740         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8741
8742         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8743
8744         /* Test suites abort the 'game' after one move */
8745         if(*appData.finger) {
8746            static FILE *f;
8747            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8748            if(!f) f = fopen(appData.finger, "w");
8749            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8750            else { DisplayFatalError("Bad output file", errno, 0); return; }
8751            free(fen);
8752            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8753         }
8754
8755         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8756         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8757             int count = 0;
8758
8759             while( count < adjudicateLossPlies ) {
8760                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8761
8762                 if( count & 1 ) {
8763                     score = -score; /* Flip score for winning side */
8764                 }
8765
8766                 if( score > appData.adjudicateLossThreshold ) {
8767                     break;
8768                 }
8769
8770                 count++;
8771             }
8772
8773             if( count >= adjudicateLossPlies ) {
8774                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8775
8776                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8777                     "Xboard adjudication",
8778                     GE_XBOARD );
8779
8780                 return;
8781             }
8782         }
8783
8784         if(Adjudicate(cps)) {
8785             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8786             return; // [HGM] adjudicate: for all automatic game ends
8787         }
8788
8789 #if ZIPPY
8790         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8791             first.initDone) {
8792           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8793                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8794                 SendToICS("draw ");
8795                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8796           }
8797           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8798           ics_user_moved = 1;
8799           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8800                 char buf[3*MSG_SIZ];
8801
8802                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8803                         programStats.score / 100.,
8804                         programStats.depth,
8805                         programStats.time / 100.,
8806                         (unsigned int)programStats.nodes,
8807                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8808                         programStats.movelist);
8809                 SendToICS(buf);
8810           }
8811         }
8812 #endif
8813
8814         /* [AS] Clear stats for next move */
8815         ClearProgramStats();
8816         thinkOutput[0] = NULLCHAR;
8817         hiddenThinkOutputState = 0;
8818
8819         bookHit = NULL;
8820         if (gameMode == TwoMachinesPlay) {
8821             /* [HGM] relaying draw offers moved to after reception of move */
8822             /* and interpreting offer as claim if it brings draw condition */
8823             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8824                 SendToProgram("draw\n", cps->other);
8825             }
8826             if (cps->other->sendTime) {
8827                 SendTimeRemaining(cps->other,
8828                                   cps->other->twoMachinesColor[0] == 'w');
8829             }
8830             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8831             if (firstMove && !bookHit) {
8832                 firstMove = FALSE;
8833                 if (cps->other->useColors) {
8834                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8835                 }
8836                 SendToProgram("go\n", cps->other);
8837             }
8838             cps->other->maybeThinking = TRUE;
8839         }
8840
8841         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8842
8843         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8844
8845         if (!pausing && appData.ringBellAfterMoves) {
8846             if(!roar) RingBell();
8847         }
8848
8849         /*
8850          * Reenable menu items that were disabled while
8851          * machine was thinking
8852          */
8853         if (gameMode != TwoMachinesPlay)
8854             SetUserThinkingEnables();
8855
8856         // [HGM] book: after book hit opponent has received move and is now in force mode
8857         // force the book reply into it, and then fake that it outputted this move by jumping
8858         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8859         if(bookHit) {
8860                 static char bookMove[MSG_SIZ]; // a bit generous?
8861
8862                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8863                 strcat(bookMove, bookHit);
8864                 message = bookMove;
8865                 cps = cps->other;
8866                 programStats.nodes = programStats.depth = programStats.time =
8867                 programStats.score = programStats.got_only_move = 0;
8868                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8869
8870                 if(cps->lastPing != cps->lastPong) {
8871                     savedMessage = message; // args for deferred call
8872                     savedState = cps;
8873                     ScheduleDelayedEvent(DeferredBookMove, 10);
8874                     return;
8875                 }
8876                 goto FakeBookMove;
8877         }
8878
8879         return;
8880     }
8881
8882     /* Set special modes for chess engines.  Later something general
8883      *  could be added here; for now there is just one kludge feature,
8884      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8885      *  when "xboard" is given as an interactive command.
8886      */
8887     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8888         cps->useSigint = FALSE;
8889         cps->useSigterm = FALSE;
8890     }
8891     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8892       ParseFeatures(message+8, cps);
8893       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8894     }
8895
8896     if (!strncmp(message, "setup ", 6) && 
8897         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8898           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8899                                         ) { // [HGM] allow first engine to define opening position
8900       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8901       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8902       *buf = NULLCHAR;
8903       if(sscanf(message, "setup (%s", buf) == 1) {
8904         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8905         ASSIGN(appData.pieceToCharTable, buf);
8906       }
8907       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8908       if(dummy >= 3) {
8909         while(message[s] && message[s++] != ' ');
8910         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8911            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8912             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8913             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8914           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8915           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8916           startedFromSetupPosition = FALSE;
8917         }
8918       }
8919       if(startedFromSetupPosition) return;
8920       ParseFEN(boards[0], &dummy, message+s, FALSE);
8921       DrawPosition(TRUE, boards[0]);
8922       CopyBoard(initialPosition, boards[0]);
8923       startedFromSetupPosition = TRUE;
8924       return;
8925     }
8926     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
8927       ChessSquare piece = WhitePawn;
8928       char *p=buf2;
8929       if(*p == '+') piece = CHUPROMOTED WhitePawn, p++;
8930       piece += CharToPiece(*p) - WhitePawn;
8931       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
8932       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
8933       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
8934       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
8935       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
8936       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
8937                                                && gameInfo.variant != VariantGreat
8938                                                && gameInfo.variant != VariantFairy    ) return;
8939       if(piece < EmptySquare) {
8940         pieceDefs = TRUE;
8941         ASSIGN(pieceDesc[piece], buf1);
8942         if(isupper(*p) && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
8943       }
8944       return;
8945     }
8946     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8947      * want this, I was asked to put it in, and obliged.
8948      */
8949     if (!strncmp(message, "setboard ", 9)) {
8950         Board initial_position;
8951
8952         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8953
8954         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8955             DisplayError(_("Bad FEN received from engine"), 0);
8956             return ;
8957         } else {
8958            Reset(TRUE, FALSE);
8959            CopyBoard(boards[0], initial_position);
8960            initialRulePlies = FENrulePlies;
8961            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8962            else gameMode = MachinePlaysBlack;
8963            DrawPosition(FALSE, boards[currentMove]);
8964         }
8965         return;
8966     }
8967
8968     /*
8969      * Look for communication commands
8970      */
8971     if (!strncmp(message, "telluser ", 9)) {
8972         if(message[9] == '\\' && message[10] == '\\')
8973             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8974         PlayTellSound();
8975         DisplayNote(message + 9);
8976         return;
8977     }
8978     if (!strncmp(message, "tellusererror ", 14)) {
8979         cps->userError = 1;
8980         if(message[14] == '\\' && message[15] == '\\')
8981             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8982         PlayTellSound();
8983         DisplayError(message + 14, 0);
8984         return;
8985     }
8986     if (!strncmp(message, "tellopponent ", 13)) {
8987       if (appData.icsActive) {
8988         if (loggedOn) {
8989           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8990           SendToICS(buf1);
8991         }
8992       } else {
8993         DisplayNote(message + 13);
8994       }
8995       return;
8996     }
8997     if (!strncmp(message, "tellothers ", 11)) {
8998       if (appData.icsActive) {
8999         if (loggedOn) {
9000           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9001           SendToICS(buf1);
9002         }
9003       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9004       return;
9005     }
9006     if (!strncmp(message, "tellall ", 8)) {
9007       if (appData.icsActive) {
9008         if (loggedOn) {
9009           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9010           SendToICS(buf1);
9011         }
9012       } else {
9013         DisplayNote(message + 8);
9014       }
9015       return;
9016     }
9017     if (strncmp(message, "warning", 7) == 0) {
9018         /* Undocumented feature, use tellusererror in new code */
9019         DisplayError(message, 0);
9020         return;
9021     }
9022     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9023         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9024         strcat(realname, " query");
9025         AskQuestion(realname, buf2, buf1, cps->pr);
9026         return;
9027     }
9028     /* Commands from the engine directly to ICS.  We don't allow these to be
9029      *  sent until we are logged on. Crafty kibitzes have been known to
9030      *  interfere with the login process.
9031      */
9032     if (loggedOn) {
9033         if (!strncmp(message, "tellics ", 8)) {
9034             SendToICS(message + 8);
9035             SendToICS("\n");
9036             return;
9037         }
9038         if (!strncmp(message, "tellicsnoalias ", 15)) {
9039             SendToICS(ics_prefix);
9040             SendToICS(message + 15);
9041             SendToICS("\n");
9042             return;
9043         }
9044         /* The following are for backward compatibility only */
9045         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9046             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9047             SendToICS(ics_prefix);
9048             SendToICS(message);
9049             SendToICS("\n");
9050             return;
9051         }
9052     }
9053     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9054         if(initPing == cps->lastPong) {
9055             if(gameInfo.variant == VariantUnknown) {
9056                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9057                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9058                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9059             }
9060             initPing = -1;
9061         }
9062         return;
9063     }
9064     if(!strncmp(message, "highlight ", 10)) {
9065         if(appData.testLegality && appData.markers) return;
9066         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9067         return;
9068     }
9069     if(!strncmp(message, "click ", 6)) {
9070         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9071         if(appData.testLegality || !appData.oneClick) return;
9072         sscanf(message+6, "%c%d%c", &f, &y, &c);
9073         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9074         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9075         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9076         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9077         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9078         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9079             LeftClick(Release, lastLeftX, lastLeftY);
9080         controlKey  = (c == ',');
9081         LeftClick(Press, x, y);
9082         LeftClick(Release, x, y);
9083         first.highlight = f;
9084         return;
9085     }
9086     /*
9087      * If the move is illegal, cancel it and redraw the board.
9088      * Also deal with other error cases.  Matching is rather loose
9089      * here to accommodate engines written before the spec.
9090      */
9091     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9092         strncmp(message, "Error", 5) == 0) {
9093         if (StrStr(message, "name") ||
9094             StrStr(message, "rating") || StrStr(message, "?") ||
9095             StrStr(message, "result") || StrStr(message, "board") ||
9096             StrStr(message, "bk") || StrStr(message, "computer") ||
9097             StrStr(message, "variant") || StrStr(message, "hint") ||
9098             StrStr(message, "random") || StrStr(message, "depth") ||
9099             StrStr(message, "accepted")) {
9100             return;
9101         }
9102         if (StrStr(message, "protover")) {
9103           /* Program is responding to input, so it's apparently done
9104              initializing, and this error message indicates it is
9105              protocol version 1.  So we don't need to wait any longer
9106              for it to initialize and send feature commands. */
9107           FeatureDone(cps, 1);
9108           cps->protocolVersion = 1;
9109           return;
9110         }
9111         cps->maybeThinking = FALSE;
9112
9113         if (StrStr(message, "draw")) {
9114             /* Program doesn't have "draw" command */
9115             cps->sendDrawOffers = 0;
9116             return;
9117         }
9118         if (cps->sendTime != 1 &&
9119             (StrStr(message, "time") || StrStr(message, "otim"))) {
9120           /* Program apparently doesn't have "time" or "otim" command */
9121           cps->sendTime = 0;
9122           return;
9123         }
9124         if (StrStr(message, "analyze")) {
9125             cps->analysisSupport = FALSE;
9126             cps->analyzing = FALSE;
9127 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9128             EditGameEvent(); // [HGM] try to preserve loaded game
9129             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9130             DisplayError(buf2, 0);
9131             return;
9132         }
9133         if (StrStr(message, "(no matching move)st")) {
9134           /* Special kludge for GNU Chess 4 only */
9135           cps->stKludge = TRUE;
9136           SendTimeControl(cps, movesPerSession, timeControl,
9137                           timeIncrement, appData.searchDepth,
9138                           searchTime);
9139           return;
9140         }
9141         if (StrStr(message, "(no matching move)sd")) {
9142           /* Special kludge for GNU Chess 4 only */
9143           cps->sdKludge = TRUE;
9144           SendTimeControl(cps, movesPerSession, timeControl,
9145                           timeIncrement, appData.searchDepth,
9146                           searchTime);
9147           return;
9148         }
9149         if (!StrStr(message, "llegal")) {
9150             return;
9151         }
9152         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9153             gameMode == IcsIdle) return;
9154         if (forwardMostMove <= backwardMostMove) return;
9155         if (pausing) PauseEvent();
9156       if(appData.forceIllegal) {
9157             // [HGM] illegal: machine refused move; force position after move into it
9158           SendToProgram("force\n", cps);
9159           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9160                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9161                 // when black is to move, while there might be nothing on a2 or black
9162                 // might already have the move. So send the board as if white has the move.
9163                 // But first we must change the stm of the engine, as it refused the last move
9164                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9165                 if(WhiteOnMove(forwardMostMove)) {
9166                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9167                     SendBoard(cps, forwardMostMove); // kludgeless board
9168                 } else {
9169                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9170                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9171                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9172                 }
9173           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9174             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9175                  gameMode == TwoMachinesPlay)
9176               SendToProgram("go\n", cps);
9177             return;
9178       } else
9179         if (gameMode == PlayFromGameFile) {
9180             /* Stop reading this game file */
9181             gameMode = EditGame;
9182             ModeHighlight();
9183         }
9184         /* [HGM] illegal-move claim should forfeit game when Xboard */
9185         /* only passes fully legal moves                            */
9186         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9187             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9188                                 "False illegal-move claim", GE_XBOARD );
9189             return; // do not take back move we tested as valid
9190         }
9191         currentMove = forwardMostMove-1;
9192         DisplayMove(currentMove-1); /* before DisplayMoveError */
9193         SwitchClocks(forwardMostMove-1); // [HGM] race
9194         DisplayBothClocks();
9195         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9196                 parseList[currentMove], _(cps->which));
9197         DisplayMoveError(buf1);
9198         DrawPosition(FALSE, boards[currentMove]);
9199
9200         SetUserThinkingEnables();
9201         return;
9202     }
9203     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9204         /* Program has a broken "time" command that
9205            outputs a string not ending in newline.
9206            Don't use it. */
9207         cps->sendTime = 0;
9208     }
9209     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9210         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9211             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9212     }
9213
9214     /*
9215      * If chess program startup fails, exit with an error message.
9216      * Attempts to recover here are futile. [HGM] Well, we try anyway
9217      */
9218     if ((StrStr(message, "unknown host") != NULL)
9219         || (StrStr(message, "No remote directory") != NULL)
9220         || (StrStr(message, "not found") != NULL)
9221         || (StrStr(message, "No such file") != NULL)
9222         || (StrStr(message, "can't alloc") != NULL)
9223         || (StrStr(message, "Permission denied") != NULL)) {
9224
9225         cps->maybeThinking = FALSE;
9226         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9227                 _(cps->which), cps->program, cps->host, message);
9228         RemoveInputSource(cps->isr);
9229         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9230             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9231             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9232         }
9233         return;
9234     }
9235
9236     /*
9237      * Look for hint output
9238      */
9239     if (sscanf(message, "Hint: %s", buf1) == 1) {
9240         if (cps == &first && hintRequested) {
9241             hintRequested = FALSE;
9242             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9243                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9244                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9245                                     PosFlags(forwardMostMove),
9246                                     fromY, fromX, toY, toX, promoChar, buf1);
9247                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9248                 DisplayInformation(buf2);
9249             } else {
9250                 /* Hint move could not be parsed!? */
9251               snprintf(buf2, sizeof(buf2),
9252                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9253                         buf1, _(cps->which));
9254                 DisplayError(buf2, 0);
9255             }
9256         } else {
9257           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9258         }
9259         return;
9260     }
9261
9262     /*
9263      * Ignore other messages if game is not in progress
9264      */
9265     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9266         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9267
9268     /*
9269      * look for win, lose, draw, or draw offer
9270      */
9271     if (strncmp(message, "1-0", 3) == 0) {
9272         char *p, *q, *r = "";
9273         p = strchr(message, '{');
9274         if (p) {
9275             q = strchr(p, '}');
9276             if (q) {
9277                 *q = NULLCHAR;
9278                 r = p + 1;
9279             }
9280         }
9281         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9282         return;
9283     } else if (strncmp(message, "0-1", 3) == 0) {
9284         char *p, *q, *r = "";
9285         p = strchr(message, '{');
9286         if (p) {
9287             q = strchr(p, '}');
9288             if (q) {
9289                 *q = NULLCHAR;
9290                 r = p + 1;
9291             }
9292         }
9293         /* Kludge for Arasan 4.1 bug */
9294         if (strcmp(r, "Black resigns") == 0) {
9295             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9296             return;
9297         }
9298         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9299         return;
9300     } else if (strncmp(message, "1/2", 3) == 0) {
9301         char *p, *q, *r = "";
9302         p = strchr(message, '{');
9303         if (p) {
9304             q = strchr(p, '}');
9305             if (q) {
9306                 *q = NULLCHAR;
9307                 r = p + 1;
9308             }
9309         }
9310
9311         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9312         return;
9313
9314     } else if (strncmp(message, "White resign", 12) == 0) {
9315         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9316         return;
9317     } else if (strncmp(message, "Black resign", 12) == 0) {
9318         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9319         return;
9320     } else if (strncmp(message, "White matches", 13) == 0 ||
9321                strncmp(message, "Black matches", 13) == 0   ) {
9322         /* [HGM] ignore GNUShogi noises */
9323         return;
9324     } else if (strncmp(message, "White", 5) == 0 &&
9325                message[5] != '(' &&
9326                StrStr(message, "Black") == NULL) {
9327         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9328         return;
9329     } else if (strncmp(message, "Black", 5) == 0 &&
9330                message[5] != '(') {
9331         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9332         return;
9333     } else if (strcmp(message, "resign") == 0 ||
9334                strcmp(message, "computer resigns") == 0) {
9335         switch (gameMode) {
9336           case MachinePlaysBlack:
9337           case IcsPlayingBlack:
9338             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9339             break;
9340           case MachinePlaysWhite:
9341           case IcsPlayingWhite:
9342             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9343             break;
9344           case TwoMachinesPlay:
9345             if (cps->twoMachinesColor[0] == 'w')
9346               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9347             else
9348               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9349             break;
9350           default:
9351             /* can't happen */
9352             break;
9353         }
9354         return;
9355     } else if (strncmp(message, "opponent mates", 14) == 0) {
9356         switch (gameMode) {
9357           case MachinePlaysBlack:
9358           case IcsPlayingBlack:
9359             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9360             break;
9361           case MachinePlaysWhite:
9362           case IcsPlayingWhite:
9363             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9364             break;
9365           case TwoMachinesPlay:
9366             if (cps->twoMachinesColor[0] == 'w')
9367               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9368             else
9369               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9370             break;
9371           default:
9372             /* can't happen */
9373             break;
9374         }
9375         return;
9376     } else if (strncmp(message, "computer mates", 14) == 0) {
9377         switch (gameMode) {
9378           case MachinePlaysBlack:
9379           case IcsPlayingBlack:
9380             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9381             break;
9382           case MachinePlaysWhite:
9383           case IcsPlayingWhite:
9384             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9385             break;
9386           case TwoMachinesPlay:
9387             if (cps->twoMachinesColor[0] == 'w')
9388               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9389             else
9390               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9391             break;
9392           default:
9393             /* can't happen */
9394             break;
9395         }
9396         return;
9397     } else if (strncmp(message, "checkmate", 9) == 0) {
9398         if (WhiteOnMove(forwardMostMove)) {
9399             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9400         } else {
9401             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9402         }
9403         return;
9404     } else if (strstr(message, "Draw") != NULL ||
9405                strstr(message, "game is a draw") != NULL) {
9406         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9407         return;
9408     } else if (strstr(message, "offer") != NULL &&
9409                strstr(message, "draw") != NULL) {
9410 #if ZIPPY
9411         if (appData.zippyPlay && first.initDone) {
9412             /* Relay offer to ICS */
9413             SendToICS(ics_prefix);
9414             SendToICS("draw\n");
9415         }
9416 #endif
9417         cps->offeredDraw = 2; /* valid until this engine moves twice */
9418         if (gameMode == TwoMachinesPlay) {
9419             if (cps->other->offeredDraw) {
9420                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9421             /* [HGM] in two-machine mode we delay relaying draw offer      */
9422             /* until after we also have move, to see if it is really claim */
9423             }
9424         } else if (gameMode == MachinePlaysWhite ||
9425                    gameMode == MachinePlaysBlack) {
9426           if (userOfferedDraw) {
9427             DisplayInformation(_("Machine accepts your draw offer"));
9428             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9429           } else {
9430             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9431           }
9432         }
9433     }
9434
9435
9436     /*
9437      * Look for thinking output
9438      */
9439     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9440           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9441                                 ) {
9442         int plylev, mvleft, mvtot, curscore, time;
9443         char mvname[MOVE_LEN];
9444         u64 nodes; // [DM]
9445         char plyext;
9446         int ignore = FALSE;
9447         int prefixHint = FALSE;
9448         mvname[0] = NULLCHAR;
9449
9450         switch (gameMode) {
9451           case MachinePlaysBlack:
9452           case IcsPlayingBlack:
9453             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9454             break;
9455           case MachinePlaysWhite:
9456           case IcsPlayingWhite:
9457             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9458             break;
9459           case AnalyzeMode:
9460           case AnalyzeFile:
9461             break;
9462           case IcsObserving: /* [DM] icsEngineAnalyze */
9463             if (!appData.icsEngineAnalyze) ignore = TRUE;
9464             break;
9465           case TwoMachinesPlay:
9466             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9467                 ignore = TRUE;
9468             }
9469             break;
9470           default:
9471             ignore = TRUE;
9472             break;
9473         }
9474
9475         if (!ignore) {
9476             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9477             buf1[0] = NULLCHAR;
9478             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9479                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9480
9481                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9482                     nodes += u64Const(0x100000000);
9483
9484                 if (plyext != ' ' && plyext != '\t') {
9485                     time *= 100;
9486                 }
9487
9488                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9489                 if( cps->scoreIsAbsolute &&
9490                     ( gameMode == MachinePlaysBlack ||
9491                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9492                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9493                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9494                      !WhiteOnMove(currentMove)
9495                     ) )
9496                 {
9497                     curscore = -curscore;
9498                 }
9499
9500                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9501
9502                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9503                         char buf[MSG_SIZ];
9504                         FILE *f;
9505                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9506                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9507                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9508                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9509                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9510                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9511                                 fclose(f);
9512                         }
9513                         else
9514                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9515                           DisplayError(_("failed writing PV"), 0);
9516                 }
9517
9518                 tempStats.depth = plylev;
9519                 tempStats.nodes = nodes;
9520                 tempStats.time = time;
9521                 tempStats.score = curscore;
9522                 tempStats.got_only_move = 0;
9523
9524                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9525                         int ticklen;
9526
9527                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9528                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9529                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9530                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9531                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9532                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9533                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9534                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9535                 }
9536
9537                 /* Buffer overflow protection */
9538                 if (pv[0] != NULLCHAR) {
9539                     if (strlen(pv) >= sizeof(tempStats.movelist)
9540                         && appData.debugMode) {
9541                         fprintf(debugFP,
9542                                 "PV is too long; using the first %u bytes.\n",
9543                                 (unsigned) sizeof(tempStats.movelist) - 1);
9544                     }
9545
9546                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9547                 } else {
9548                     sprintf(tempStats.movelist, " no PV\n");
9549                 }
9550
9551                 if (tempStats.seen_stat) {
9552                     tempStats.ok_to_send = 1;
9553                 }
9554
9555                 if (strchr(tempStats.movelist, '(') != NULL) {
9556                     tempStats.line_is_book = 1;
9557                     tempStats.nr_moves = 0;
9558                     tempStats.moves_left = 0;
9559                 } else {
9560                     tempStats.line_is_book = 0;
9561                 }
9562
9563                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9564                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9565
9566                 SendProgramStatsToFrontend( cps, &tempStats );
9567
9568                 /*
9569                     [AS] Protect the thinkOutput buffer from overflow... this
9570                     is only useful if buf1 hasn't overflowed first!
9571                 */
9572                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9573                          plylev,
9574                          (gameMode == TwoMachinesPlay ?
9575                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9576                          ((double) curscore) / 100.0,
9577                          prefixHint ? lastHint : "",
9578                          prefixHint ? " " : "" );
9579
9580                 if( buf1[0] != NULLCHAR ) {
9581                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9582
9583                     if( strlen(pv) > max_len ) {
9584                         if( appData.debugMode) {
9585                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9586                         }
9587                         pv[max_len+1] = '\0';
9588                     }
9589
9590                     strcat( thinkOutput, pv);
9591                 }
9592
9593                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9594                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9595                     DisplayMove(currentMove - 1);
9596                 }
9597                 return;
9598
9599             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9600                 /* crafty (9.25+) says "(only move) <move>"
9601                  * if there is only 1 legal move
9602                  */
9603                 sscanf(p, "(only move) %s", buf1);
9604                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9605                 sprintf(programStats.movelist, "%s (only move)", buf1);
9606                 programStats.depth = 1;
9607                 programStats.nr_moves = 1;
9608                 programStats.moves_left = 1;
9609                 programStats.nodes = 1;
9610                 programStats.time = 1;
9611                 programStats.got_only_move = 1;
9612
9613                 /* Not really, but we also use this member to
9614                    mean "line isn't going to change" (Crafty
9615                    isn't searching, so stats won't change) */
9616                 programStats.line_is_book = 1;
9617
9618                 SendProgramStatsToFrontend( cps, &programStats );
9619
9620                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9621                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9622                     DisplayMove(currentMove - 1);
9623                 }
9624                 return;
9625             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9626                               &time, &nodes, &plylev, &mvleft,
9627                               &mvtot, mvname) >= 5) {
9628                 /* The stat01: line is from Crafty (9.29+) in response
9629                    to the "." command */
9630                 programStats.seen_stat = 1;
9631                 cps->maybeThinking = TRUE;
9632
9633                 if (programStats.got_only_move || !appData.periodicUpdates)
9634                   return;
9635
9636                 programStats.depth = plylev;
9637                 programStats.time = time;
9638                 programStats.nodes = nodes;
9639                 programStats.moves_left = mvleft;
9640                 programStats.nr_moves = mvtot;
9641                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9642                 programStats.ok_to_send = 1;
9643                 programStats.movelist[0] = '\0';
9644
9645                 SendProgramStatsToFrontend( cps, &programStats );
9646
9647                 return;
9648
9649             } else if (strncmp(message,"++",2) == 0) {
9650                 /* Crafty 9.29+ outputs this */
9651                 programStats.got_fail = 2;
9652                 return;
9653
9654             } else if (strncmp(message,"--",2) == 0) {
9655                 /* Crafty 9.29+ outputs this */
9656                 programStats.got_fail = 1;
9657                 return;
9658
9659             } else if (thinkOutput[0] != NULLCHAR &&
9660                        strncmp(message, "    ", 4) == 0) {
9661                 unsigned message_len;
9662
9663                 p = message;
9664                 while (*p && *p == ' ') p++;
9665
9666                 message_len = strlen( p );
9667
9668                 /* [AS] Avoid buffer overflow */
9669                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9670                     strcat(thinkOutput, " ");
9671                     strcat(thinkOutput, p);
9672                 }
9673
9674                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9675                     strcat(programStats.movelist, " ");
9676                     strcat(programStats.movelist, p);
9677                 }
9678
9679                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9680                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9681                     DisplayMove(currentMove - 1);
9682                 }
9683                 return;
9684             }
9685         }
9686         else {
9687             buf1[0] = NULLCHAR;
9688
9689             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9690                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9691             {
9692                 ChessProgramStats cpstats;
9693
9694                 if (plyext != ' ' && plyext != '\t') {
9695                     time *= 100;
9696                 }
9697
9698                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9699                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9700                     curscore = -curscore;
9701                 }
9702
9703                 cpstats.depth = plylev;
9704                 cpstats.nodes = nodes;
9705                 cpstats.time = time;
9706                 cpstats.score = curscore;
9707                 cpstats.got_only_move = 0;
9708                 cpstats.movelist[0] = '\0';
9709
9710                 if (buf1[0] != NULLCHAR) {
9711                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9712                 }
9713
9714                 cpstats.ok_to_send = 0;
9715                 cpstats.line_is_book = 0;
9716                 cpstats.nr_moves = 0;
9717                 cpstats.moves_left = 0;
9718
9719                 SendProgramStatsToFrontend( cps, &cpstats );
9720             }
9721         }
9722     }
9723 }
9724
9725
9726 /* Parse a game score from the character string "game", and
9727    record it as the history of the current game.  The game
9728    score is NOT assumed to start from the standard position.
9729    The display is not updated in any way.
9730    */
9731 void
9732 ParseGameHistory (char *game)
9733 {
9734     ChessMove moveType;
9735     int fromX, fromY, toX, toY, boardIndex;
9736     char promoChar;
9737     char *p, *q;
9738     char buf[MSG_SIZ];
9739
9740     if (appData.debugMode)
9741       fprintf(debugFP, "Parsing game history: %s\n", game);
9742
9743     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9744     gameInfo.site = StrSave(appData.icsHost);
9745     gameInfo.date = PGNDate();
9746     gameInfo.round = StrSave("-");
9747
9748     /* Parse out names of players */
9749     while (*game == ' ') game++;
9750     p = buf;
9751     while (*game != ' ') *p++ = *game++;
9752     *p = NULLCHAR;
9753     gameInfo.white = StrSave(buf);
9754     while (*game == ' ') game++;
9755     p = buf;
9756     while (*game != ' ' && *game != '\n') *p++ = *game++;
9757     *p = NULLCHAR;
9758     gameInfo.black = StrSave(buf);
9759
9760     /* Parse moves */
9761     boardIndex = blackPlaysFirst ? 1 : 0;
9762     yynewstr(game);
9763     for (;;) {
9764         yyboardindex = boardIndex;
9765         moveType = (ChessMove) Myylex();
9766         switch (moveType) {
9767           case IllegalMove:             /* maybe suicide chess, etc. */
9768   if (appData.debugMode) {
9769     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9770     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9771     setbuf(debugFP, NULL);
9772   }
9773           case WhitePromotion:
9774           case BlackPromotion:
9775           case WhiteNonPromotion:
9776           case BlackNonPromotion:
9777           case NormalMove:
9778           case FirstLeg:
9779           case WhiteCapturesEnPassant:
9780           case BlackCapturesEnPassant:
9781           case WhiteKingSideCastle:
9782           case WhiteQueenSideCastle:
9783           case BlackKingSideCastle:
9784           case BlackQueenSideCastle:
9785           case WhiteKingSideCastleWild:
9786           case WhiteQueenSideCastleWild:
9787           case BlackKingSideCastleWild:
9788           case BlackQueenSideCastleWild:
9789           /* PUSH Fabien */
9790           case WhiteHSideCastleFR:
9791           case WhiteASideCastleFR:
9792           case BlackHSideCastleFR:
9793           case BlackASideCastleFR:
9794           /* POP Fabien */
9795             fromX = currentMoveString[0] - AAA;
9796             fromY = currentMoveString[1] - ONE;
9797             toX = currentMoveString[2] - AAA;
9798             toY = currentMoveString[3] - ONE;
9799             promoChar = currentMoveString[4];
9800             break;
9801           case WhiteDrop:
9802           case BlackDrop:
9803             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9804             fromX = moveType == WhiteDrop ?
9805               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9806             (int) CharToPiece(ToLower(currentMoveString[0]));
9807             fromY = DROP_RANK;
9808             toX = currentMoveString[2] - AAA;
9809             toY = currentMoveString[3] - ONE;
9810             promoChar = NULLCHAR;
9811             break;
9812           case AmbiguousMove:
9813             /* bug? */
9814             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9815   if (appData.debugMode) {
9816     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9817     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9818     setbuf(debugFP, NULL);
9819   }
9820             DisplayError(buf, 0);
9821             return;
9822           case ImpossibleMove:
9823             /* bug? */
9824             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9825   if (appData.debugMode) {
9826     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9827     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9828     setbuf(debugFP, NULL);
9829   }
9830             DisplayError(buf, 0);
9831             return;
9832           case EndOfFile:
9833             if (boardIndex < backwardMostMove) {
9834                 /* Oops, gap.  How did that happen? */
9835                 DisplayError(_("Gap in move list"), 0);
9836                 return;
9837             }
9838             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9839             if (boardIndex > forwardMostMove) {
9840                 forwardMostMove = boardIndex;
9841             }
9842             return;
9843           case ElapsedTime:
9844             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9845                 strcat(parseList[boardIndex-1], " ");
9846                 strcat(parseList[boardIndex-1], yy_text);
9847             }
9848             continue;
9849           case Comment:
9850           case PGNTag:
9851           case NAG:
9852           default:
9853             /* ignore */
9854             continue;
9855           case WhiteWins:
9856           case BlackWins:
9857           case GameIsDrawn:
9858           case GameUnfinished:
9859             if (gameMode == IcsExamining) {
9860                 if (boardIndex < backwardMostMove) {
9861                     /* Oops, gap.  How did that happen? */
9862                     return;
9863                 }
9864                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9865                 return;
9866             }
9867             gameInfo.result = moveType;
9868             p = strchr(yy_text, '{');
9869             if (p == NULL) p = strchr(yy_text, '(');
9870             if (p == NULL) {
9871                 p = yy_text;
9872                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9873             } else {
9874                 q = strchr(p, *p == '{' ? '}' : ')');
9875                 if (q != NULL) *q = NULLCHAR;
9876                 p++;
9877             }
9878             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9879             gameInfo.resultDetails = StrSave(p);
9880             continue;
9881         }
9882         if (boardIndex >= forwardMostMove &&
9883             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9884             backwardMostMove = blackPlaysFirst ? 1 : 0;
9885             return;
9886         }
9887         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9888                                  fromY, fromX, toY, toX, promoChar,
9889                                  parseList[boardIndex]);
9890         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9891         /* currentMoveString is set as a side-effect of yylex */
9892         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9893         strcat(moveList[boardIndex], "\n");
9894         boardIndex++;
9895         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9896         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9897           case MT_NONE:
9898           case MT_STALEMATE:
9899           default:
9900             break;
9901           case MT_CHECK:
9902             if(!IS_SHOGI(gameInfo.variant))
9903                 strcat(parseList[boardIndex - 1], "+");
9904             break;
9905           case MT_CHECKMATE:
9906           case MT_STAINMATE:
9907             strcat(parseList[boardIndex - 1], "#");
9908             break;
9909         }
9910     }
9911 }
9912
9913
9914 /* Apply a move to the given board  */
9915 void
9916 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9917 {
9918   ChessSquare captured = board[toY][toX], piece, pawn, king, killed; int p, rookX, oldEP, epRank, berolina = 0;
9919   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9920
9921     /* [HGM] compute & store e.p. status and castling rights for new position */
9922     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9923
9924       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9925       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
9926       board[EP_STATUS] = EP_NONE;
9927       board[EP_FILE] = board[EP_RANK] = 100;
9928
9929   if (fromY == DROP_RANK) {
9930         /* must be first */
9931         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9932             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9933             return;
9934         }
9935         piece = board[toY][toX] = (ChessSquare) fromX;
9936   } else {
9937 //      ChessSquare victim;
9938       int i;
9939
9940       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9941 //           victim = board[killY][killX],
9942            killed = board[killY][killX],
9943            board[killY][killX] = EmptySquare,
9944            board[EP_STATUS] = EP_CAPTURE;
9945
9946       if( board[toY][toX] != EmptySquare ) {
9947            board[EP_STATUS] = EP_CAPTURE;
9948            if( (fromX != toX || fromY != toY) && // not igui!
9949                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9950                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9951                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9952            }
9953       }
9954
9955       pawn = board[fromY][fromX];
9956       if( pawn == WhiteLance || pawn == BlackLance ) {
9957            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
9958                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
9959                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
9960            }
9961       }
9962       if( pawn == WhitePawn ) {
9963            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9964                board[EP_STATUS] = EP_PAWN_MOVE;
9965            if( toY-fromY>=2) {
9966                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
9967                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9968                         gameInfo.variant != VariantBerolina || toX < fromX)
9969                       board[EP_STATUS] = toX | berolina;
9970                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9971                         gameInfo.variant != VariantBerolina || toX > fromX)
9972                       board[EP_STATUS] = toX;
9973            }
9974       } else
9975       if( pawn == BlackPawn ) {
9976            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9977                board[EP_STATUS] = EP_PAWN_MOVE;
9978            if( toY-fromY<= -2) {
9979                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
9980                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9981                         gameInfo.variant != VariantBerolina || toX < fromX)
9982                       board[EP_STATUS] = toX | berolina;
9983                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9984                         gameInfo.variant != VariantBerolina || toX > fromX)
9985                       board[EP_STATUS] = toX;
9986            }
9987        }
9988
9989        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
9990        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
9991        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
9992        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
9993
9994        for(i=0; i<nrCastlingRights; i++) {
9995            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9996               board[CASTLING][i] == toX   && castlingRank[i] == toY
9997              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9998        }
9999
10000        if(gameInfo.variant == VariantSChess) { // update virginity
10001            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10002            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10003            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10004            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10005        }
10006
10007      if (fromX == toX && fromY == toY) return;
10008
10009      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10010      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10011      if(gameInfo.variant == VariantKnightmate)
10012          king += (int) WhiteUnicorn - (int) WhiteKing;
10013
10014     if(piece != WhiteKing && piece != BlackKing && pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10015        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and captures own
10016         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10017         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10018         board[EP_STATUS] = EP_NONE; // capture was fake!
10019     } else
10020     /* Code added by Tord: */
10021     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10022     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10023         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10024       board[EP_STATUS] = EP_NONE; // capture was fake!
10025       board[fromY][fromX] = EmptySquare;
10026       board[toY][toX] = EmptySquare;
10027       if((toX > fromX) != (piece == WhiteRook)) {
10028         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10029       } else {
10030         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10031       }
10032     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10033                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10034       board[EP_STATUS] = EP_NONE;
10035       board[fromY][fromX] = EmptySquare;
10036       board[toY][toX] = EmptySquare;
10037       if((toX > fromX) != (piece == BlackRook)) {
10038         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10039       } else {
10040         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10041       }
10042     /* End of code added by Tord */
10043
10044     } else if (board[fromY][fromX] == king
10045         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10046         && toY == fromY && toX > fromX+1) {
10047         board[fromY][fromX] = EmptySquare;
10048         board[toY][toX] = king;
10049         for(rookX=BOARD_RGHT-1; board[toY][rookX] == DarkSquare && rookX > toX + 1; rookX--);
10050         board[toY][toX-1] = board[fromY][rookX];
10051         board[fromY][rookX] = EmptySquare;
10052     } else if (board[fromY][fromX] == king
10053         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10054                && toY == fromY && toX < fromX-1) {
10055         board[fromY][fromX] = EmptySquare;
10056         board[toY][toX] = king;
10057         for(rookX=BOARD_LEFT; board[toY][rookX] == DarkSquare && rookX < toX - 1; rookX++);
10058         board[toY][toX+1] = board[fromY][rookX];
10059         board[fromY][rookX] = EmptySquare;
10060     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10061                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10062                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10063                ) {
10064         /* white pawn promotion */
10065         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10066         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10067             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10068         board[fromY][fromX] = EmptySquare;
10069     } else if ((fromY >= BOARD_HEIGHT>>1)
10070                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10071                && (toX != fromX)
10072                && gameInfo.variant != VariantXiangqi
10073                && gameInfo.variant != VariantBerolina
10074                && (pawn == WhitePawn)
10075                && (board[toY][toX] == EmptySquare)) {
10076         board[fromY][fromX] = EmptySquare;
10077         board[toY][toX] = piece;
10078         if(toY == epRank - 128 + 1)
10079             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10080         else
10081             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10082     } else if ((fromY == BOARD_HEIGHT-4)
10083                && (toX == fromX)
10084                && gameInfo.variant == VariantBerolina
10085                && (board[fromY][fromX] == WhitePawn)
10086                && (board[toY][toX] == EmptySquare)) {
10087         board[fromY][fromX] = EmptySquare;
10088         board[toY][toX] = WhitePawn;
10089         if(oldEP & EP_BEROLIN_A) {
10090                 captured = board[fromY][fromX-1];
10091                 board[fromY][fromX-1] = EmptySquare;
10092         }else{  captured = board[fromY][fromX+1];
10093                 board[fromY][fromX+1] = EmptySquare;
10094         }
10095     } else if (board[fromY][fromX] == king
10096         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10097                && toY == fromY && toX > fromX+1) {
10098         board[fromY][fromX] = EmptySquare;
10099         board[toY][toX] = king;
10100         for(rookX=BOARD_RGHT-1; board[toY][rookX] == DarkSquare && rookX > toX + 1; rookX--);
10101         board[toY][toX-1] = board[fromY][rookX];
10102         board[fromY][rookX] = EmptySquare;
10103     } else if (board[fromY][fromX] == king
10104         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10105                && toY == fromY && toX < fromX-1) {
10106         board[fromY][fromX] = EmptySquare;
10107         board[toY][toX] = king;
10108         for(rookX=BOARD_LEFT; board[toY][rookX] == DarkSquare && rookX < toX - 1; rookX++);
10109         board[toY][toX+1] = board[fromY][rookX];
10110         board[fromY][rookX] = EmptySquare;
10111     } else if (fromY == 7 && fromX == 3
10112                && board[fromY][fromX] == BlackKing
10113                && toY == 7 && toX == 5) {
10114         board[fromY][fromX] = EmptySquare;
10115         board[toY][toX] = BlackKing;
10116         board[fromY][7] = EmptySquare;
10117         board[toY][4] = BlackRook;
10118     } else if (fromY == 7 && fromX == 3
10119                && board[fromY][fromX] == BlackKing
10120                && toY == 7 && toX == 1) {
10121         board[fromY][fromX] = EmptySquare;
10122         board[toY][toX] = BlackKing;
10123         board[fromY][0] = EmptySquare;
10124         board[toY][2] = BlackRook;
10125     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10126                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10127                && toY < promoRank && promoChar
10128                ) {
10129         /* black pawn promotion */
10130         board[toY][toX] = CharToPiece(ToLower(promoChar));
10131         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10132             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10133         board[fromY][fromX] = EmptySquare;
10134     } else if ((fromY < BOARD_HEIGHT>>1)
10135                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10136                && (toX != fromX)
10137                && gameInfo.variant != VariantXiangqi
10138                && gameInfo.variant != VariantBerolina
10139                && (pawn == BlackPawn)
10140                && (board[toY][toX] == EmptySquare)) {
10141         board[fromY][fromX] = EmptySquare;
10142         board[toY][toX] = piece;
10143         if(toY == epRank - 128 - 1)
10144             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10145         else
10146             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10147     } else if ((fromY == 3)
10148                && (toX == fromX)
10149                && gameInfo.variant == VariantBerolina
10150                && (board[fromY][fromX] == BlackPawn)
10151                && (board[toY][toX] == EmptySquare)) {
10152         board[fromY][fromX] = EmptySquare;
10153         board[toY][toX] = BlackPawn;
10154         if(oldEP & EP_BEROLIN_A) {
10155                 captured = board[fromY][fromX-1];
10156                 board[fromY][fromX-1] = EmptySquare;
10157         }else{  captured = board[fromY][fromX+1];
10158                 board[fromY][fromX+1] = EmptySquare;
10159         }
10160     } else {
10161         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10162         board[fromY][fromX] = EmptySquare;
10163         board[toY][toX] = piece;
10164     }
10165   }
10166
10167     if (gameInfo.holdingsWidth != 0) {
10168
10169       /* !!A lot more code needs to be written to support holdings  */
10170       /* [HGM] OK, so I have written it. Holdings are stored in the */
10171       /* penultimate board files, so they are automaticlly stored   */
10172       /* in the game history.                                       */
10173       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10174                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10175         /* Delete from holdings, by decreasing count */
10176         /* and erasing image if necessary            */
10177         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10178         if(p < (int) BlackPawn) { /* white drop */
10179              p -= (int)WhitePawn;
10180                  p = PieceToNumber((ChessSquare)p);
10181              if(p >= gameInfo.holdingsSize) p = 0;
10182              if(--board[p][BOARD_WIDTH-2] <= 0)
10183                   board[p][BOARD_WIDTH-1] = EmptySquare;
10184              if((int)board[p][BOARD_WIDTH-2] < 0)
10185                         board[p][BOARD_WIDTH-2] = 0;
10186         } else {                  /* black drop */
10187              p -= (int)BlackPawn;
10188                  p = PieceToNumber((ChessSquare)p);
10189              if(p >= gameInfo.holdingsSize) p = 0;
10190              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10191                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10192              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10193                         board[BOARD_HEIGHT-1-p][1] = 0;
10194         }
10195       }
10196       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10197           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10198         /* [HGM] holdings: Add to holdings, if holdings exist */
10199         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10200                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10201                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10202         }
10203         p = (int) captured;
10204         if (p >= (int) BlackPawn) {
10205           p -= (int)BlackPawn;
10206           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10207                   /* Restore shogi-promoted piece to its original  first */
10208                   captured = (ChessSquare) (DEMOTED captured);
10209                   p = DEMOTED p;
10210           }
10211           p = PieceToNumber((ChessSquare)p);
10212           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10213           board[p][BOARD_WIDTH-2]++;
10214           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10215         } else {
10216           p -= (int)WhitePawn;
10217           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10218                   captured = (ChessSquare) (DEMOTED captured);
10219                   p = DEMOTED p;
10220           }
10221           p = PieceToNumber((ChessSquare)p);
10222           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10223           board[BOARD_HEIGHT-1-p][1]++;
10224           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10225         }
10226       }
10227     } else if (gameInfo.variant == VariantAtomic) {
10228       if (captured != EmptySquare) {
10229         int y, x;
10230         for (y = toY-1; y <= toY+1; y++) {
10231           for (x = toX-1; x <= toX+1; x++) {
10232             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10233                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10234               board[y][x] = EmptySquare;
10235             }
10236           }
10237         }
10238         board[toY][toX] = EmptySquare;
10239       }
10240     }
10241
10242     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10243         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10244     } else
10245     if(promoChar == '+') {
10246         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10247         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10248         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10249           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10250     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10251         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10252         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10253            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10254         board[toY][toX] = newPiece;
10255     }
10256     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10257                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10258         // [HGM] superchess: take promotion piece out of holdings
10259         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10260         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10261             if(!--board[k][BOARD_WIDTH-2])
10262                 board[k][BOARD_WIDTH-1] = EmptySquare;
10263         } else {
10264             if(!--board[BOARD_HEIGHT-1-k][1])
10265                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10266         }
10267     }
10268 }
10269
10270 /* Updates forwardMostMove */
10271 void
10272 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10273 {
10274     int x = toX, y = toY;
10275     char *s = parseList[forwardMostMove];
10276     ChessSquare p = boards[forwardMostMove][toY][toX];
10277 //    forwardMostMove++; // [HGM] bare: moved downstream
10278
10279     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10280     (void) CoordsToAlgebraic(boards[forwardMostMove],
10281                              PosFlags(forwardMostMove),
10282                              fromY, fromX, y, x, promoChar,
10283                              s);
10284     if(killX >= 0 && killY >= 0)
10285         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10286
10287     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10288         int timeLeft; static int lastLoadFlag=0; int king, piece;
10289         piece = boards[forwardMostMove][fromY][fromX];
10290         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10291         if(gameInfo.variant == VariantKnightmate)
10292             king += (int) WhiteUnicorn - (int) WhiteKing;
10293         if(forwardMostMove == 0) {
10294             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10295                 fprintf(serverMoves, "%s;", UserName());
10296             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10297                 fprintf(serverMoves, "%s;", second.tidy);
10298             fprintf(serverMoves, "%s;", first.tidy);
10299             if(gameMode == MachinePlaysWhite)
10300                 fprintf(serverMoves, "%s;", UserName());
10301             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10302                 fprintf(serverMoves, "%s;", second.tidy);
10303         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10304         lastLoadFlag = loadFlag;
10305         // print base move
10306         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10307         // print castling suffix
10308         if( toY == fromY && piece == king ) {
10309             if(toX-fromX > 1)
10310                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10311             if(fromX-toX >1)
10312                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10313         }
10314         // e.p. suffix
10315         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10316              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10317              boards[forwardMostMove][toY][toX] == EmptySquare
10318              && fromX != toX && fromY != toY)
10319                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10320         // promotion suffix
10321         if(promoChar != NULLCHAR) {
10322             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10323                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10324                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10325             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10326         }
10327         if(!loadFlag) {
10328                 char buf[MOVE_LEN*2], *p; int len;
10329             fprintf(serverMoves, "/%d/%d",
10330                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10331             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10332             else                      timeLeft = blackTimeRemaining/1000;
10333             fprintf(serverMoves, "/%d", timeLeft);
10334                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10335                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10336                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10337                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10338             fprintf(serverMoves, "/%s", buf);
10339         }
10340         fflush(serverMoves);
10341     }
10342
10343     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10344         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10345       return;
10346     }
10347     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10348     if (commentList[forwardMostMove+1] != NULL) {
10349         free(commentList[forwardMostMove+1]);
10350         commentList[forwardMostMove+1] = NULL;
10351     }
10352     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10353     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10354     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10355     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10356     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10357     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10358     adjustedClock = FALSE;
10359     gameInfo.result = GameUnfinished;
10360     if (gameInfo.resultDetails != NULL) {
10361         free(gameInfo.resultDetails);
10362         gameInfo.resultDetails = NULL;
10363     }
10364     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10365                               moveList[forwardMostMove - 1]);
10366     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10367       case MT_NONE:
10368       case MT_STALEMATE:
10369       default:
10370         break;
10371       case MT_CHECK:
10372         if(!IS_SHOGI(gameInfo.variant))
10373             strcat(parseList[forwardMostMove - 1], "+");
10374         break;
10375       case MT_CHECKMATE:
10376       case MT_STAINMATE:
10377         strcat(parseList[forwardMostMove - 1], "#");
10378         break;
10379     }
10380 }
10381
10382 /* Updates currentMove if not pausing */
10383 void
10384 ShowMove (int fromX, int fromY, int toX, int toY)
10385 {
10386     int instant = (gameMode == PlayFromGameFile) ?
10387         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10388     if(appData.noGUI) return;
10389     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10390         if (!instant) {
10391             if (forwardMostMove == currentMove + 1) {
10392                 AnimateMove(boards[forwardMostMove - 1],
10393                             fromX, fromY, toX, toY);
10394             }
10395         }
10396         currentMove = forwardMostMove;
10397     }
10398
10399     killX = killY = -1; // [HGM] lion: used up
10400
10401     if (instant) return;
10402
10403     DisplayMove(currentMove - 1);
10404     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10405             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10406                 SetHighlights(fromX, fromY, toX, toY);
10407             }
10408     }
10409     DrawPosition(FALSE, boards[currentMove]);
10410     DisplayBothClocks();
10411     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10412 }
10413
10414 void
10415 SendEgtPath (ChessProgramState *cps)
10416 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10417         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10418
10419         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10420
10421         while(*p) {
10422             char c, *q = name+1, *r, *s;
10423
10424             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10425             while(*p && *p != ',') *q++ = *p++;
10426             *q++ = ':'; *q = 0;
10427             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10428                 strcmp(name, ",nalimov:") == 0 ) {
10429                 // take nalimov path from the menu-changeable option first, if it is defined
10430               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10431                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10432             } else
10433             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10434                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10435                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10436                 s = r = StrStr(s, ":") + 1; // beginning of path info
10437                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10438                 c = *r; *r = 0;             // temporarily null-terminate path info
10439                     *--q = 0;               // strip of trailig ':' from name
10440                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10441                 *r = c;
10442                 SendToProgram(buf,cps);     // send egtbpath command for this format
10443             }
10444             if(*p == ',') p++; // read away comma to position for next format name
10445         }
10446 }
10447
10448 static int
10449 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10450 {
10451       int width = 8, height = 8, holdings = 0;             // most common sizes
10452       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10453       // correct the deviations default for each variant
10454       if( v == VariantXiangqi ) width = 9,  height = 10;
10455       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10456       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10457       if( v == VariantCapablanca || v == VariantCapaRandom ||
10458           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10459                                 width = 10;
10460       if( v == VariantCourier ) width = 12;
10461       if( v == VariantSuper )                            holdings = 8;
10462       if( v == VariantGreat )   width = 10,              holdings = 8;
10463       if( v == VariantSChess )                           holdings = 7;
10464       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10465       if( v == VariantChuChess) width = 10, height = 10;
10466       if( v == VariantChu )     width = 12, height = 12;
10467       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10468              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10469              holdingsSize >= 0 && holdingsSize != holdings;
10470 }
10471
10472 char variantError[MSG_SIZ];
10473
10474 char *
10475 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10476 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10477       char *p, *variant = VariantName(v);
10478       static char b[MSG_SIZ];
10479       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10480            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10481                                                holdingsSize, variant); // cook up sized variant name
10482            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10483            if(StrStr(list, b) == NULL) {
10484                // specific sized variant not known, check if general sizing allowed
10485                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10486                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10487                             boardWidth, boardHeight, holdingsSize, engine);
10488                    return NULL;
10489                }
10490                /* [HGM] here we really should compare with the maximum supported board size */
10491            }
10492       } else snprintf(b, MSG_SIZ,"%s", variant);
10493       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10494       p = StrStr(list, b);
10495       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10496       if(p == NULL) {
10497           // occurs not at all in list, or only as sub-string
10498           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10499           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10500               int l = strlen(variantError);
10501               char *q;
10502               while(p != list && p[-1] != ',') p--;
10503               q = strchr(p, ',');
10504               if(q) *q = NULLCHAR;
10505               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10506               if(q) *q= ',';
10507           }
10508           return NULL;
10509       }
10510       return b;
10511 }
10512
10513 void
10514 InitChessProgram (ChessProgramState *cps, int setup)
10515 /* setup needed to setup FRC opening position */
10516 {
10517     char buf[MSG_SIZ], *b;
10518     if (appData.noChessProgram) return;
10519     hintRequested = FALSE;
10520     bookRequested = FALSE;
10521
10522     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10523     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10524     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10525     if(cps->memSize) { /* [HGM] memory */
10526       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10527         SendToProgram(buf, cps);
10528     }
10529     SendEgtPath(cps); /* [HGM] EGT */
10530     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10531       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10532         SendToProgram(buf, cps);
10533     }
10534
10535     setboardSpoiledMachineBlack = FALSE;
10536     SendToProgram(cps->initString, cps);
10537     if (gameInfo.variant != VariantNormal &&
10538         gameInfo.variant != VariantLoadable
10539         /* [HGM] also send variant if board size non-standard */
10540         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10541
10542       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10543                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10544       if (b == NULL) {
10545         VariantClass v;
10546         char c, *q = cps->variants, *p = strchr(q, ',');
10547         if(p) *p = NULLCHAR;
10548         v = StringToVariant(q);
10549         DisplayError(variantError, 0);
10550         if(v != VariantUnknown && cps == &first) {
10551             int w, h, s;
10552             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10553                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10554             ASSIGN(appData.variant, q);
10555             Reset(TRUE, FALSE);
10556         }
10557         if(p) *p = ',';
10558         return;
10559       }
10560
10561       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10562       SendToProgram(buf, cps);
10563     }
10564     currentlyInitializedVariant = gameInfo.variant;
10565
10566     /* [HGM] send opening position in FRC to first engine */
10567     if(setup) {
10568           SendToProgram("force\n", cps);
10569           SendBoard(cps, 0);
10570           /* engine is now in force mode! Set flag to wake it up after first move. */
10571           setboardSpoiledMachineBlack = 1;
10572     }
10573
10574     if (cps->sendICS) {
10575       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10576       SendToProgram(buf, cps);
10577     }
10578     cps->maybeThinking = FALSE;
10579     cps->offeredDraw = 0;
10580     if (!appData.icsActive) {
10581         SendTimeControl(cps, movesPerSession, timeControl,
10582                         timeIncrement, appData.searchDepth,
10583                         searchTime);
10584     }
10585     if (appData.showThinking
10586         // [HGM] thinking: four options require thinking output to be sent
10587         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10588                                 ) {
10589         SendToProgram("post\n", cps);
10590     }
10591     SendToProgram("hard\n", cps);
10592     if (!appData.ponderNextMove) {
10593         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10594            it without being sure what state we are in first.  "hard"
10595            is not a toggle, so that one is OK.
10596          */
10597         SendToProgram("easy\n", cps);
10598     }
10599     if (cps->usePing) {
10600       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10601       SendToProgram(buf, cps);
10602     }
10603     cps->initDone = TRUE;
10604     ClearEngineOutputPane(cps == &second);
10605 }
10606
10607
10608 void
10609 ResendOptions (ChessProgramState *cps)
10610 { // send the stored value of the options
10611   int i;
10612   char buf[MSG_SIZ];
10613   Option *opt = cps->option;
10614   for(i=0; i<cps->nrOptions; i++, opt++) {
10615       switch(opt->type) {
10616         case Spin:
10617         case Slider:
10618         case CheckBox:
10619             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10620           break;
10621         case ComboBox:
10622           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10623           break;
10624         default:
10625             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10626           break;
10627         case Button:
10628         case SaveButton:
10629           continue;
10630       }
10631       SendToProgram(buf, cps);
10632   }
10633 }
10634
10635 void
10636 StartChessProgram (ChessProgramState *cps)
10637 {
10638     char buf[MSG_SIZ];
10639     int err;
10640
10641     if (appData.noChessProgram) return;
10642     cps->initDone = FALSE;
10643
10644     if (strcmp(cps->host, "localhost") == 0) {
10645         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10646     } else if (*appData.remoteShell == NULLCHAR) {
10647         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10648     } else {
10649         if (*appData.remoteUser == NULLCHAR) {
10650           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10651                     cps->program);
10652         } else {
10653           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10654                     cps->host, appData.remoteUser, cps->program);
10655         }
10656         err = StartChildProcess(buf, "", &cps->pr);
10657     }
10658
10659     if (err != 0) {
10660       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10661         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10662         if(cps != &first) return;
10663         appData.noChessProgram = TRUE;
10664         ThawUI();
10665         SetNCPMode();
10666 //      DisplayFatalError(buf, err, 1);
10667 //      cps->pr = NoProc;
10668 //      cps->isr = NULL;
10669         return;
10670     }
10671
10672     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10673     if (cps->protocolVersion > 1) {
10674       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10675       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10676         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10677         cps->comboCnt = 0;  //                and values of combo boxes
10678       }
10679       SendToProgram(buf, cps);
10680       if(cps->reload) ResendOptions(cps);
10681     } else {
10682       SendToProgram("xboard\n", cps);
10683     }
10684 }
10685
10686 void
10687 TwoMachinesEventIfReady P((void))
10688 {
10689   static int curMess = 0;
10690   if (first.lastPing != first.lastPong) {
10691     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10692     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10693     return;
10694   }
10695   if (second.lastPing != second.lastPong) {
10696     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10697     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10698     return;
10699   }
10700   DisplayMessage("", ""); curMess = 0;
10701   TwoMachinesEvent();
10702 }
10703
10704 char *
10705 MakeName (char *template)
10706 {
10707     time_t clock;
10708     struct tm *tm;
10709     static char buf[MSG_SIZ];
10710     char *p = buf;
10711     int i;
10712
10713     clock = time((time_t *)NULL);
10714     tm = localtime(&clock);
10715
10716     while(*p++ = *template++) if(p[-1] == '%') {
10717         switch(*template++) {
10718           case 0:   *p = 0; return buf;
10719           case 'Y': i = tm->tm_year+1900; break;
10720           case 'y': i = tm->tm_year-100; break;
10721           case 'M': i = tm->tm_mon+1; break;
10722           case 'd': i = tm->tm_mday; break;
10723           case 'h': i = tm->tm_hour; break;
10724           case 'm': i = tm->tm_min; break;
10725           case 's': i = tm->tm_sec; break;
10726           default:  i = 0;
10727         }
10728         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10729     }
10730     return buf;
10731 }
10732
10733 int
10734 CountPlayers (char *p)
10735 {
10736     int n = 0;
10737     while(p = strchr(p, '\n')) p++, n++; // count participants
10738     return n;
10739 }
10740
10741 FILE *
10742 WriteTourneyFile (char *results, FILE *f)
10743 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10744     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10745     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10746         // create a file with tournament description
10747         fprintf(f, "-participants {%s}\n", appData.participants);
10748         fprintf(f, "-seedBase %d\n", appData.seedBase);
10749         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10750         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10751         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10752         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10753         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10754         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10755         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10756         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10757         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10758         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10759         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10760         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10761         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10762         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10763         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10764         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10765         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10766         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10767         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10768         fprintf(f, "-smpCores %d\n", appData.smpCores);
10769         if(searchTime > 0)
10770                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10771         else {
10772                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10773                 fprintf(f, "-tc %s\n", appData.timeControl);
10774                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10775         }
10776         fprintf(f, "-results \"%s\"\n", results);
10777     }
10778     return f;
10779 }
10780
10781 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10782
10783 void
10784 Substitute (char *participants, int expunge)
10785 {
10786     int i, changed, changes=0, nPlayers=0;
10787     char *p, *q, *r, buf[MSG_SIZ];
10788     if(participants == NULL) return;
10789     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10790     r = p = participants; q = appData.participants;
10791     while(*p && *p == *q) {
10792         if(*p == '\n') r = p+1, nPlayers++;
10793         p++; q++;
10794     }
10795     if(*p) { // difference
10796         while(*p && *p++ != '\n');
10797         while(*q && *q++ != '\n');
10798       changed = nPlayers;
10799         changes = 1 + (strcmp(p, q) != 0);
10800     }
10801     if(changes == 1) { // a single engine mnemonic was changed
10802         q = r; while(*q) nPlayers += (*q++ == '\n');
10803         p = buf; while(*r && (*p = *r++) != '\n') p++;
10804         *p = NULLCHAR;
10805         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10806         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10807         if(mnemonic[i]) { // The substitute is valid
10808             FILE *f;
10809             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10810                 flock(fileno(f), LOCK_EX);
10811                 ParseArgsFromFile(f);
10812                 fseek(f, 0, SEEK_SET);
10813                 FREE(appData.participants); appData.participants = participants;
10814                 if(expunge) { // erase results of replaced engine
10815                     int len = strlen(appData.results), w, b, dummy;
10816                     for(i=0; i<len; i++) {
10817                         Pairing(i, nPlayers, &w, &b, &dummy);
10818                         if((w == changed || b == changed) && appData.results[i] == '*') {
10819                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10820                             fclose(f);
10821                             return;
10822                         }
10823                     }
10824                     for(i=0; i<len; i++) {
10825                         Pairing(i, nPlayers, &w, &b, &dummy);
10826                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10827                     }
10828                 }
10829                 WriteTourneyFile(appData.results, f);
10830                 fclose(f); // release lock
10831                 return;
10832             }
10833         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10834     }
10835     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10836     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10837     free(participants);
10838     return;
10839 }
10840
10841 int
10842 CheckPlayers (char *participants)
10843 {
10844         int i;
10845         char buf[MSG_SIZ], *p;
10846         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10847         while(p = strchr(participants, '\n')) {
10848             *p = NULLCHAR;
10849             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10850             if(!mnemonic[i]) {
10851                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10852                 *p = '\n';
10853                 DisplayError(buf, 0);
10854                 return 1;
10855             }
10856             *p = '\n';
10857             participants = p + 1;
10858         }
10859         return 0;
10860 }
10861
10862 int
10863 CreateTourney (char *name)
10864 {
10865         FILE *f;
10866         if(matchMode && strcmp(name, appData.tourneyFile)) {
10867              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10868         }
10869         if(name[0] == NULLCHAR) {
10870             if(appData.participants[0])
10871                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10872             return 0;
10873         }
10874         f = fopen(name, "r");
10875         if(f) { // file exists
10876             ASSIGN(appData.tourneyFile, name);
10877             ParseArgsFromFile(f); // parse it
10878         } else {
10879             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10880             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10881                 DisplayError(_("Not enough participants"), 0);
10882                 return 0;
10883             }
10884             if(CheckPlayers(appData.participants)) return 0;
10885             ASSIGN(appData.tourneyFile, name);
10886             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10887             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10888         }
10889         fclose(f);
10890         appData.noChessProgram = FALSE;
10891         appData.clockMode = TRUE;
10892         SetGNUMode();
10893         return 1;
10894 }
10895
10896 int
10897 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10898 {
10899     char buf[MSG_SIZ], *p, *q;
10900     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10901     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10902     skip = !all && group[0]; // if group requested, we start in skip mode
10903     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10904         p = names; q = buf; header = 0;
10905         while(*p && *p != '\n') *q++ = *p++;
10906         *q = 0;
10907         if(*p == '\n') p++;
10908         if(buf[0] == '#') {
10909             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10910             depth++; // we must be entering a new group
10911             if(all) continue; // suppress printing group headers when complete list requested
10912             header = 1;
10913             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10914         }
10915         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10916         if(engineList[i]) free(engineList[i]);
10917         engineList[i] = strdup(buf);
10918         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10919         if(engineMnemonic[i]) free(engineMnemonic[i]);
10920         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10921             strcat(buf, " (");
10922             sscanf(q + 8, "%s", buf + strlen(buf));
10923             strcat(buf, ")");
10924         }
10925         engineMnemonic[i] = strdup(buf);
10926         i++;
10927     }
10928     engineList[i] = engineMnemonic[i] = NULL;
10929     return i;
10930 }
10931
10932 // following implemented as macro to avoid type limitations
10933 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10934
10935 void
10936 SwapEngines (int n)
10937 {   // swap settings for first engine and other engine (so far only some selected options)
10938     int h;
10939     char *p;
10940     if(n == 0) return;
10941     SWAP(directory, p)
10942     SWAP(chessProgram, p)
10943     SWAP(isUCI, h)
10944     SWAP(hasOwnBookUCI, h)
10945     SWAP(protocolVersion, h)
10946     SWAP(reuse, h)
10947     SWAP(scoreIsAbsolute, h)
10948     SWAP(timeOdds, h)
10949     SWAP(logo, p)
10950     SWAP(pgnName, p)
10951     SWAP(pvSAN, h)
10952     SWAP(engOptions, p)
10953     SWAP(engInitString, p)
10954     SWAP(computerString, p)
10955     SWAP(features, p)
10956     SWAP(fenOverride, p)
10957     SWAP(NPS, h)
10958     SWAP(accumulateTC, h)
10959     SWAP(drawDepth, h)
10960     SWAP(host, p)
10961     SWAP(pseudo, h)
10962 }
10963
10964 int
10965 GetEngineLine (char *s, int n)
10966 {
10967     int i;
10968     char buf[MSG_SIZ];
10969     extern char *icsNames;
10970     if(!s || !*s) return 0;
10971     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10972     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10973     if(!mnemonic[i]) return 0;
10974     if(n == 11) return 1; // just testing if there was a match
10975     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10976     if(n == 1) SwapEngines(n);
10977     ParseArgsFromString(buf);
10978     if(n == 1) SwapEngines(n);
10979     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10980         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10981         ParseArgsFromString(buf);
10982     }
10983     return 1;
10984 }
10985
10986 int
10987 SetPlayer (int player, char *p)
10988 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10989     int i;
10990     char buf[MSG_SIZ], *engineName;
10991     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10992     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10993     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10994     if(mnemonic[i]) {
10995         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10996         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10997         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10998         ParseArgsFromString(buf);
10999     } else { // no engine with this nickname is installed!
11000         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11001         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11002         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11003         ModeHighlight();
11004         DisplayError(buf, 0);
11005         return 0;
11006     }
11007     free(engineName);
11008     return i;
11009 }
11010
11011 char *recentEngines;
11012
11013 void
11014 RecentEngineEvent (int nr)
11015 {
11016     int n;
11017 //    SwapEngines(1); // bump first to second
11018 //    ReplaceEngine(&second, 1); // and load it there
11019     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11020     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11021     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11022         ReplaceEngine(&first, 0);
11023         FloatToFront(&appData.recentEngineList, command[n]);
11024     }
11025 }
11026
11027 int
11028 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11029 {   // determine players from game number
11030     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11031
11032     if(appData.tourneyType == 0) {
11033         roundsPerCycle = (nPlayers - 1) | 1;
11034         pairingsPerRound = nPlayers / 2;
11035     } else if(appData.tourneyType > 0) {
11036         roundsPerCycle = nPlayers - appData.tourneyType;
11037         pairingsPerRound = appData.tourneyType;
11038     }
11039     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11040     gamesPerCycle = gamesPerRound * roundsPerCycle;
11041     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11042     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11043     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11044     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11045     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11046     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11047
11048     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11049     if(appData.roundSync) *syncInterval = gamesPerRound;
11050
11051     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11052
11053     if(appData.tourneyType == 0) {
11054         if(curPairing == (nPlayers-1)/2 ) {
11055             *whitePlayer = curRound;
11056             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11057         } else {
11058             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11059             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11060             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11061             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11062         }
11063     } else if(appData.tourneyType > 1) {
11064         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11065         *whitePlayer = curRound + appData.tourneyType;
11066     } else if(appData.tourneyType > 0) {
11067         *whitePlayer = curPairing;
11068         *blackPlayer = curRound + appData.tourneyType;
11069     }
11070
11071     // take care of white/black alternation per round.
11072     // For cycles and games this is already taken care of by default, derived from matchGame!
11073     return curRound & 1;
11074 }
11075
11076 int
11077 NextTourneyGame (int nr, int *swapColors)
11078 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11079     char *p, *q;
11080     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11081     FILE *tf;
11082     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11083     tf = fopen(appData.tourneyFile, "r");
11084     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11085     ParseArgsFromFile(tf); fclose(tf);
11086     InitTimeControls(); // TC might be altered from tourney file
11087
11088     nPlayers = CountPlayers(appData.participants); // count participants
11089     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11090     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11091
11092     if(syncInterval) {
11093         p = q = appData.results;
11094         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11095         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11096             DisplayMessage(_("Waiting for other game(s)"),"");
11097             waitingForGame = TRUE;
11098             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11099             return 0;
11100         }
11101         waitingForGame = FALSE;
11102     }
11103
11104     if(appData.tourneyType < 0) {
11105         if(nr>=0 && !pairingReceived) {
11106             char buf[1<<16];
11107             if(pairing.pr == NoProc) {
11108                 if(!appData.pairingEngine[0]) {
11109                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11110                     return 0;
11111                 }
11112                 StartChessProgram(&pairing); // starts the pairing engine
11113             }
11114             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11115             SendToProgram(buf, &pairing);
11116             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11117             SendToProgram(buf, &pairing);
11118             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11119         }
11120         pairingReceived = 0;                              // ... so we continue here
11121         *swapColors = 0;
11122         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11123         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11124         matchGame = 1; roundNr = nr / syncInterval + 1;
11125     }
11126
11127     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11128
11129     // redefine engines, engine dir, etc.
11130     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11131     if(first.pr == NoProc) {
11132       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11133       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11134     }
11135     if(second.pr == NoProc) {
11136       SwapEngines(1);
11137       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11138       SwapEngines(1);         // and make that valid for second engine by swapping
11139       InitEngine(&second, 1);
11140     }
11141     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11142     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11143     return OK;
11144 }
11145
11146 void
11147 NextMatchGame ()
11148 {   // performs game initialization that does not invoke engines, and then tries to start the game
11149     int res, firstWhite, swapColors = 0;
11150     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11151     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
11152         char buf[MSG_SIZ];
11153         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11154         if(strcmp(buf, currentDebugFile)) { // name has changed
11155             FILE *f = fopen(buf, "w");
11156             if(f) { // if opening the new file failed, just keep using the old one
11157                 ASSIGN(currentDebugFile, buf);
11158                 fclose(debugFP);
11159                 debugFP = f;
11160             }
11161             if(appData.serverFileName) {
11162                 if(serverFP) fclose(serverFP);
11163                 serverFP = fopen(appData.serverFileName, "w");
11164                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11165                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11166             }
11167         }
11168     }
11169     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11170     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11171     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11172     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11173     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11174     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11175     Reset(FALSE, first.pr != NoProc);
11176     res = LoadGameOrPosition(matchGame); // setup game
11177     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11178     if(!res) return; // abort when bad game/pos file
11179     TwoMachinesEvent();
11180 }
11181
11182 void
11183 UserAdjudicationEvent (int result)
11184 {
11185     ChessMove gameResult = GameIsDrawn;
11186
11187     if( result > 0 ) {
11188         gameResult = WhiteWins;
11189     }
11190     else if( result < 0 ) {
11191         gameResult = BlackWins;
11192     }
11193
11194     if( gameMode == TwoMachinesPlay ) {
11195         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11196     }
11197 }
11198
11199
11200 // [HGM] save: calculate checksum of game to make games easily identifiable
11201 int
11202 StringCheckSum (char *s)
11203 {
11204         int i = 0;
11205         if(s==NULL) return 0;
11206         while(*s) i = i*259 + *s++;
11207         return i;
11208 }
11209
11210 int
11211 GameCheckSum ()
11212 {
11213         int i, sum=0;
11214         for(i=backwardMostMove; i<forwardMostMove; i++) {
11215                 sum += pvInfoList[i].depth;
11216                 sum += StringCheckSum(parseList[i]);
11217                 sum += StringCheckSum(commentList[i]);
11218                 sum *= 261;
11219         }
11220         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11221         return sum + StringCheckSum(commentList[i]);
11222 } // end of save patch
11223
11224 void
11225 GameEnds (ChessMove result, char *resultDetails, int whosays)
11226 {
11227     GameMode nextGameMode;
11228     int isIcsGame;
11229     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11230
11231     if(endingGame) return; /* [HGM] crash: forbid recursion */
11232     endingGame = 1;
11233     if(twoBoards) { // [HGM] dual: switch back to one board
11234         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11235         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11236     }
11237     if (appData.debugMode) {
11238       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11239               result, resultDetails ? resultDetails : "(null)", whosays);
11240     }
11241
11242     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11243
11244     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11245
11246     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11247         /* If we are playing on ICS, the server decides when the
11248            game is over, but the engine can offer to draw, claim
11249            a draw, or resign.
11250          */
11251 #if ZIPPY
11252         if (appData.zippyPlay && first.initDone) {
11253             if (result == GameIsDrawn) {
11254                 /* In case draw still needs to be claimed */
11255                 SendToICS(ics_prefix);
11256                 SendToICS("draw\n");
11257             } else if (StrCaseStr(resultDetails, "resign")) {
11258                 SendToICS(ics_prefix);
11259                 SendToICS("resign\n");
11260             }
11261         }
11262 #endif
11263         endingGame = 0; /* [HGM] crash */
11264         return;
11265     }
11266
11267     /* If we're loading the game from a file, stop */
11268     if (whosays == GE_FILE) {
11269       (void) StopLoadGameTimer();
11270       gameFileFP = NULL;
11271     }
11272
11273     /* Cancel draw offers */
11274     first.offeredDraw = second.offeredDraw = 0;
11275
11276     /* If this is an ICS game, only ICS can really say it's done;
11277        if not, anyone can. */
11278     isIcsGame = (gameMode == IcsPlayingWhite ||
11279                  gameMode == IcsPlayingBlack ||
11280                  gameMode == IcsObserving    ||
11281                  gameMode == IcsExamining);
11282
11283     if (!isIcsGame || whosays == GE_ICS) {
11284         /* OK -- not an ICS game, or ICS said it was done */
11285         StopClocks();
11286         if (!isIcsGame && !appData.noChessProgram)
11287           SetUserThinkingEnables();
11288
11289         /* [HGM] if a machine claims the game end we verify this claim */
11290         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11291             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11292                 char claimer;
11293                 ChessMove trueResult = (ChessMove) -1;
11294
11295                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11296                                             first.twoMachinesColor[0] :
11297                                             second.twoMachinesColor[0] ;
11298
11299                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11300                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11301                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11302                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11303                 } else
11304                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11305                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11306                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11307                 } else
11308                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11309                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11310                 }
11311
11312                 // now verify win claims, but not in drop games, as we don't understand those yet
11313                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11314                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11315                     (result == WhiteWins && claimer == 'w' ||
11316                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11317                       if (appData.debugMode) {
11318                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11319                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11320                       }
11321                       if(result != trueResult) {
11322                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11323                               result = claimer == 'w' ? BlackWins : WhiteWins;
11324                               resultDetails = buf;
11325                       }
11326                 } else
11327                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11328                     && (forwardMostMove <= backwardMostMove ||
11329                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11330                         (claimer=='b')==(forwardMostMove&1))
11331                                                                                   ) {
11332                       /* [HGM] verify: draws that were not flagged are false claims */
11333                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11334                       result = claimer == 'w' ? BlackWins : WhiteWins;
11335                       resultDetails = buf;
11336                 }
11337                 /* (Claiming a loss is accepted no questions asked!) */
11338             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11339                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11340                 result = GameUnfinished;
11341                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11342             }
11343             /* [HGM] bare: don't allow bare King to win */
11344             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11345                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11346                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11347                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11348                && result != GameIsDrawn)
11349             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11350                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11351                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11352                         if(p >= 0 && p <= (int)WhiteKing) k++;
11353                 }
11354                 if (appData.debugMode) {
11355                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11356                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11357                 }
11358                 if(k <= 1) {
11359                         result = GameIsDrawn;
11360                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11361                         resultDetails = buf;
11362                 }
11363             }
11364         }
11365
11366
11367         if(serverMoves != NULL && !loadFlag) { char c = '=';
11368             if(result==WhiteWins) c = '+';
11369             if(result==BlackWins) c = '-';
11370             if(resultDetails != NULL)
11371                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11372         }
11373         if (resultDetails != NULL) {
11374             gameInfo.result = result;
11375             gameInfo.resultDetails = StrSave(resultDetails);
11376
11377             /* display last move only if game was not loaded from file */
11378             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11379                 DisplayMove(currentMove - 1);
11380
11381             if (forwardMostMove != 0) {
11382                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11383                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11384                                                                 ) {
11385                     if (*appData.saveGameFile != NULLCHAR) {
11386                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11387                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11388                         else
11389                         SaveGameToFile(appData.saveGameFile, TRUE);
11390                     } else if (appData.autoSaveGames) {
11391                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11392                     }
11393                     if (*appData.savePositionFile != NULLCHAR) {
11394                         SavePositionToFile(appData.savePositionFile);
11395                     }
11396                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11397                 }
11398             }
11399
11400             /* Tell program how game ended in case it is learning */
11401             /* [HGM] Moved this to after saving the PGN, just in case */
11402             /* engine died and we got here through time loss. In that */
11403             /* case we will get a fatal error writing the pipe, which */
11404             /* would otherwise lose us the PGN.                       */
11405             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11406             /* output during GameEnds should never be fatal anymore   */
11407             if (gameMode == MachinePlaysWhite ||
11408                 gameMode == MachinePlaysBlack ||
11409                 gameMode == TwoMachinesPlay ||
11410                 gameMode == IcsPlayingWhite ||
11411                 gameMode == IcsPlayingBlack ||
11412                 gameMode == BeginningOfGame) {
11413                 char buf[MSG_SIZ];
11414                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11415                         resultDetails);
11416                 if (first.pr != NoProc) {
11417                     SendToProgram(buf, &first);
11418                 }
11419                 if (second.pr != NoProc &&
11420                     gameMode == TwoMachinesPlay) {
11421                     SendToProgram(buf, &second);
11422                 }
11423             }
11424         }
11425
11426         if (appData.icsActive) {
11427             if (appData.quietPlay &&
11428                 (gameMode == IcsPlayingWhite ||
11429                  gameMode == IcsPlayingBlack)) {
11430                 SendToICS(ics_prefix);
11431                 SendToICS("set shout 1\n");
11432             }
11433             nextGameMode = IcsIdle;
11434             ics_user_moved = FALSE;
11435             /* clean up premove.  It's ugly when the game has ended and the
11436              * premove highlights are still on the board.
11437              */
11438             if (gotPremove) {
11439               gotPremove = FALSE;
11440               ClearPremoveHighlights();
11441               DrawPosition(FALSE, boards[currentMove]);
11442             }
11443             if (whosays == GE_ICS) {
11444                 switch (result) {
11445                 case WhiteWins:
11446                     if (gameMode == IcsPlayingWhite)
11447                         PlayIcsWinSound();
11448                     else if(gameMode == IcsPlayingBlack)
11449                         PlayIcsLossSound();
11450                     break;
11451                 case BlackWins:
11452                     if (gameMode == IcsPlayingBlack)
11453                         PlayIcsWinSound();
11454                     else if(gameMode == IcsPlayingWhite)
11455                         PlayIcsLossSound();
11456                     break;
11457                 case GameIsDrawn:
11458                     PlayIcsDrawSound();
11459                     break;
11460                 default:
11461                     PlayIcsUnfinishedSound();
11462                 }
11463             }
11464             if(appData.quitNext) { ExitEvent(0); return; }
11465         } else if (gameMode == EditGame ||
11466                    gameMode == PlayFromGameFile ||
11467                    gameMode == AnalyzeMode ||
11468                    gameMode == AnalyzeFile) {
11469             nextGameMode = gameMode;
11470         } else {
11471             nextGameMode = EndOfGame;
11472         }
11473         pausing = FALSE;
11474         ModeHighlight();
11475     } else {
11476         nextGameMode = gameMode;
11477     }
11478
11479     if (appData.noChessProgram) {
11480         gameMode = nextGameMode;
11481         ModeHighlight();
11482         endingGame = 0; /* [HGM] crash */
11483         return;
11484     }
11485
11486     if (first.reuse) {
11487         /* Put first chess program into idle state */
11488         if (first.pr != NoProc &&
11489             (gameMode == MachinePlaysWhite ||
11490              gameMode == MachinePlaysBlack ||
11491              gameMode == TwoMachinesPlay ||
11492              gameMode == IcsPlayingWhite ||
11493              gameMode == IcsPlayingBlack ||
11494              gameMode == BeginningOfGame)) {
11495             SendToProgram("force\n", &first);
11496             if (first.usePing) {
11497               char buf[MSG_SIZ];
11498               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11499               SendToProgram(buf, &first);
11500             }
11501         }
11502     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11503         /* Kill off first chess program */
11504         if (first.isr != NULL)
11505           RemoveInputSource(first.isr);
11506         first.isr = NULL;
11507
11508         if (first.pr != NoProc) {
11509             ExitAnalyzeMode();
11510             DoSleep( appData.delayBeforeQuit );
11511             SendToProgram("quit\n", &first);
11512             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11513             first.reload = TRUE;
11514         }
11515         first.pr = NoProc;
11516     }
11517     if (second.reuse) {
11518         /* Put second chess program into idle state */
11519         if (second.pr != NoProc &&
11520             gameMode == TwoMachinesPlay) {
11521             SendToProgram("force\n", &second);
11522             if (second.usePing) {
11523               char buf[MSG_SIZ];
11524               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11525               SendToProgram(buf, &second);
11526             }
11527         }
11528     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11529         /* Kill off second chess program */
11530         if (second.isr != NULL)
11531           RemoveInputSource(second.isr);
11532         second.isr = NULL;
11533
11534         if (second.pr != NoProc) {
11535             DoSleep( appData.delayBeforeQuit );
11536             SendToProgram("quit\n", &second);
11537             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11538             second.reload = TRUE;
11539         }
11540         second.pr = NoProc;
11541     }
11542
11543     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11544         char resChar = '=';
11545         switch (result) {
11546         case WhiteWins:
11547           resChar = '+';
11548           if (first.twoMachinesColor[0] == 'w') {
11549             first.matchWins++;
11550           } else {
11551             second.matchWins++;
11552           }
11553           break;
11554         case BlackWins:
11555           resChar = '-';
11556           if (first.twoMachinesColor[0] == 'b') {
11557             first.matchWins++;
11558           } else {
11559             second.matchWins++;
11560           }
11561           break;
11562         case GameUnfinished:
11563           resChar = ' ';
11564         default:
11565           break;
11566         }
11567
11568         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11569         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11570             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11571             ReserveGame(nextGame, resChar); // sets nextGame
11572             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11573             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11574         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11575
11576         if (nextGame <= appData.matchGames && !abortMatch) {
11577             gameMode = nextGameMode;
11578             matchGame = nextGame; // this will be overruled in tourney mode!
11579             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11580             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11581             endingGame = 0; /* [HGM] crash */
11582             return;
11583         } else {
11584             gameMode = nextGameMode;
11585             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11586                      first.tidy, second.tidy,
11587                      first.matchWins, second.matchWins,
11588                      appData.matchGames - (first.matchWins + second.matchWins));
11589             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11590             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11591             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11592             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11593                 first.twoMachinesColor = "black\n";
11594                 second.twoMachinesColor = "white\n";
11595             } else {
11596                 first.twoMachinesColor = "white\n";
11597                 second.twoMachinesColor = "black\n";
11598             }
11599         }
11600     }
11601     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11602         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11603       ExitAnalyzeMode();
11604     gameMode = nextGameMode;
11605     ModeHighlight();
11606     endingGame = 0;  /* [HGM] crash */
11607     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11608         if(matchMode == TRUE) { // match through command line: exit with or without popup
11609             if(ranking) {
11610                 ToNrEvent(forwardMostMove);
11611                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11612                 else ExitEvent(0);
11613             } else DisplayFatalError(buf, 0, 0);
11614         } else { // match through menu; just stop, with or without popup
11615             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11616             ModeHighlight();
11617             if(ranking){
11618                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11619             } else DisplayNote(buf);
11620       }
11621       if(ranking) free(ranking);
11622     }
11623 }
11624
11625 /* Assumes program was just initialized (initString sent).
11626    Leaves program in force mode. */
11627 void
11628 FeedMovesToProgram (ChessProgramState *cps, int upto)
11629 {
11630     int i;
11631
11632     if (appData.debugMode)
11633       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11634               startedFromSetupPosition ? "position and " : "",
11635               backwardMostMove, upto, cps->which);
11636     if(currentlyInitializedVariant != gameInfo.variant) {
11637       char buf[MSG_SIZ];
11638         // [HGM] variantswitch: make engine aware of new variant
11639         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11640                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11641                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11642         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11643         SendToProgram(buf, cps);
11644         currentlyInitializedVariant = gameInfo.variant;
11645     }
11646     SendToProgram("force\n", cps);
11647     if (startedFromSetupPosition) {
11648         SendBoard(cps, backwardMostMove);
11649     if (appData.debugMode) {
11650         fprintf(debugFP, "feedMoves\n");
11651     }
11652     }
11653     for (i = backwardMostMove; i < upto; i++) {
11654         SendMoveToProgram(i, cps);
11655     }
11656 }
11657
11658
11659 int
11660 ResurrectChessProgram ()
11661 {
11662      /* The chess program may have exited.
11663         If so, restart it and feed it all the moves made so far. */
11664     static int doInit = 0;
11665
11666     if (appData.noChessProgram) return 1;
11667
11668     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11669         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11670         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11671         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11672     } else {
11673         if (first.pr != NoProc) return 1;
11674         StartChessProgram(&first);
11675     }
11676     InitChessProgram(&first, FALSE);
11677     FeedMovesToProgram(&first, currentMove);
11678
11679     if (!first.sendTime) {
11680         /* can't tell gnuchess what its clock should read,
11681            so we bow to its notion. */
11682         ResetClocks();
11683         timeRemaining[0][currentMove] = whiteTimeRemaining;
11684         timeRemaining[1][currentMove] = blackTimeRemaining;
11685     }
11686
11687     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11688                 appData.icsEngineAnalyze) && first.analysisSupport) {
11689       SendToProgram("analyze\n", &first);
11690       first.analyzing = TRUE;
11691     }
11692     return 1;
11693 }
11694
11695 /*
11696  * Button procedures
11697  */
11698 void
11699 Reset (int redraw, int init)
11700 {
11701     int i;
11702
11703     if (appData.debugMode) {
11704         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11705                 redraw, init, gameMode);
11706     }
11707     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11708     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11709     CleanupTail(); // [HGM] vari: delete any stored variations
11710     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11711     pausing = pauseExamInvalid = FALSE;
11712     startedFromSetupPosition = blackPlaysFirst = FALSE;
11713     firstMove = TRUE;
11714     whiteFlag = blackFlag = FALSE;
11715     userOfferedDraw = FALSE;
11716     hintRequested = bookRequested = FALSE;
11717     first.maybeThinking = FALSE;
11718     second.maybeThinking = FALSE;
11719     first.bookSuspend = FALSE; // [HGM] book
11720     second.bookSuspend = FALSE;
11721     thinkOutput[0] = NULLCHAR;
11722     lastHint[0] = NULLCHAR;
11723     ClearGameInfo(&gameInfo);
11724     gameInfo.variant = StringToVariant(appData.variant);
11725     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11726     ics_user_moved = ics_clock_paused = FALSE;
11727     ics_getting_history = H_FALSE;
11728     ics_gamenum = -1;
11729     white_holding[0] = black_holding[0] = NULLCHAR;
11730     ClearProgramStats();
11731     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11732
11733     ResetFrontEnd();
11734     ClearHighlights();
11735     flipView = appData.flipView;
11736     ClearPremoveHighlights();
11737     gotPremove = FALSE;
11738     alarmSounded = FALSE;
11739     killX = killY = -1; // [HGM] lion
11740
11741     GameEnds(EndOfFile, NULL, GE_PLAYER);
11742     if(appData.serverMovesName != NULL) {
11743         /* [HGM] prepare to make moves file for broadcasting */
11744         clock_t t = clock();
11745         if(serverMoves != NULL) fclose(serverMoves);
11746         serverMoves = fopen(appData.serverMovesName, "r");
11747         if(serverMoves != NULL) {
11748             fclose(serverMoves);
11749             /* delay 15 sec before overwriting, so all clients can see end */
11750             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11751         }
11752         serverMoves = fopen(appData.serverMovesName, "w");
11753     }
11754
11755     ExitAnalyzeMode();
11756     gameMode = BeginningOfGame;
11757     ModeHighlight();
11758     if(appData.icsActive) gameInfo.variant = VariantNormal;
11759     currentMove = forwardMostMove = backwardMostMove = 0;
11760     MarkTargetSquares(1);
11761     InitPosition(redraw);
11762     for (i = 0; i < MAX_MOVES; i++) {
11763         if (commentList[i] != NULL) {
11764             free(commentList[i]);
11765             commentList[i] = NULL;
11766         }
11767     }
11768     ResetClocks();
11769     timeRemaining[0][0] = whiteTimeRemaining;
11770     timeRemaining[1][0] = blackTimeRemaining;
11771
11772     if (first.pr == NoProc) {
11773         StartChessProgram(&first);
11774     }
11775     if (init) {
11776             InitChessProgram(&first, startedFromSetupPosition);
11777     }
11778     DisplayTitle("");
11779     DisplayMessage("", "");
11780     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11781     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11782     ClearMap();        // [HGM] exclude: invalidate map
11783 }
11784
11785 void
11786 AutoPlayGameLoop ()
11787 {
11788     for (;;) {
11789         if (!AutoPlayOneMove())
11790           return;
11791         if (matchMode || appData.timeDelay == 0)
11792           continue;
11793         if (appData.timeDelay < 0)
11794           return;
11795         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11796         break;
11797     }
11798 }
11799
11800 void
11801 AnalyzeNextGame()
11802 {
11803     ReloadGame(1); // next game
11804 }
11805
11806 int
11807 AutoPlayOneMove ()
11808 {
11809     int fromX, fromY, toX, toY;
11810
11811     if (appData.debugMode) {
11812       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11813     }
11814
11815     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11816       return FALSE;
11817
11818     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11819       pvInfoList[currentMove].depth = programStats.depth;
11820       pvInfoList[currentMove].score = programStats.score;
11821       pvInfoList[currentMove].time  = 0;
11822       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11823       else { // append analysis of final position as comment
11824         char buf[MSG_SIZ];
11825         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11826         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11827       }
11828       programStats.depth = 0;
11829     }
11830
11831     if (currentMove >= forwardMostMove) {
11832       if(gameMode == AnalyzeFile) {
11833           if(appData.loadGameIndex == -1) {
11834             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11835           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11836           } else {
11837           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11838         }
11839       }
11840 //      gameMode = EndOfGame;
11841 //      ModeHighlight();
11842
11843       /* [AS] Clear current move marker at the end of a game */
11844       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11845
11846       return FALSE;
11847     }
11848
11849     toX = moveList[currentMove][2] - AAA;
11850     toY = moveList[currentMove][3] - ONE;
11851
11852     if (moveList[currentMove][1] == '@') {
11853         if (appData.highlightLastMove) {
11854             SetHighlights(-1, -1, toX, toY);
11855         }
11856     } else {
11857         int viaX = moveList[currentMove][5] - AAA;
11858         int viaY = moveList[currentMove][6] - ONE;
11859         fromX = moveList[currentMove][0] - AAA;
11860         fromY = moveList[currentMove][1] - ONE;
11861
11862         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11863
11864         if(moveList[currentMove][4] == ';') { // multi-leg
11865             ChessSquare piece = boards[currentMove][viaY][viaX];
11866             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11867             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11868             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11869             boards[currentMove][viaY][viaX] = piece;
11870         } else
11871         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11872
11873         if (appData.highlightLastMove) {
11874             SetHighlights(fromX, fromY, toX, toY);
11875         }
11876     }
11877     DisplayMove(currentMove);
11878     SendMoveToProgram(currentMove++, &first);
11879     DisplayBothClocks();
11880     DrawPosition(FALSE, boards[currentMove]);
11881     // [HGM] PV info: always display, routine tests if empty
11882     DisplayComment(currentMove - 1, commentList[currentMove]);
11883     return TRUE;
11884 }
11885
11886
11887 int
11888 LoadGameOneMove (ChessMove readAhead)
11889 {
11890     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11891     char promoChar = NULLCHAR;
11892     ChessMove moveType;
11893     char move[MSG_SIZ];
11894     char *p, *q;
11895
11896     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11897         gameMode != AnalyzeMode && gameMode != Training) {
11898         gameFileFP = NULL;
11899         return FALSE;
11900     }
11901
11902     yyboardindex = forwardMostMove;
11903     if (readAhead != EndOfFile) {
11904       moveType = readAhead;
11905     } else {
11906       if (gameFileFP == NULL)
11907           return FALSE;
11908       moveType = (ChessMove) Myylex();
11909     }
11910
11911     done = FALSE;
11912     switch (moveType) {
11913       case Comment:
11914         if (appData.debugMode)
11915           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11916         p = yy_text;
11917
11918         /* append the comment but don't display it */
11919         AppendComment(currentMove, p, FALSE);
11920         return TRUE;
11921
11922       case WhiteCapturesEnPassant:
11923       case BlackCapturesEnPassant:
11924       case WhitePromotion:
11925       case BlackPromotion:
11926       case WhiteNonPromotion:
11927       case BlackNonPromotion:
11928       case NormalMove:
11929       case FirstLeg:
11930       case WhiteKingSideCastle:
11931       case WhiteQueenSideCastle:
11932       case BlackKingSideCastle:
11933       case BlackQueenSideCastle:
11934       case WhiteKingSideCastleWild:
11935       case WhiteQueenSideCastleWild:
11936       case BlackKingSideCastleWild:
11937       case BlackQueenSideCastleWild:
11938       /* PUSH Fabien */
11939       case WhiteHSideCastleFR:
11940       case WhiteASideCastleFR:
11941       case BlackHSideCastleFR:
11942       case BlackASideCastleFR:
11943       /* POP Fabien */
11944         if (appData.debugMode)
11945           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11946         fromX = currentMoveString[0] - AAA;
11947         fromY = currentMoveString[1] - ONE;
11948         toX = currentMoveString[2] - AAA;
11949         toY = currentMoveString[3] - ONE;
11950         promoChar = currentMoveString[4];
11951         if(promoChar == ';') promoChar = NULLCHAR;
11952         break;
11953
11954       case WhiteDrop:
11955       case BlackDrop:
11956         if (appData.debugMode)
11957           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11958         fromX = moveType == WhiteDrop ?
11959           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11960         (int) CharToPiece(ToLower(currentMoveString[0]));
11961         fromY = DROP_RANK;
11962         toX = currentMoveString[2] - AAA;
11963         toY = currentMoveString[3] - ONE;
11964         break;
11965
11966       case WhiteWins:
11967       case BlackWins:
11968       case GameIsDrawn:
11969       case GameUnfinished:
11970         if (appData.debugMode)
11971           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11972         p = strchr(yy_text, '{');
11973         if (p == NULL) p = strchr(yy_text, '(');
11974         if (p == NULL) {
11975             p = yy_text;
11976             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11977         } else {
11978             q = strchr(p, *p == '{' ? '}' : ')');
11979             if (q != NULL) *q = NULLCHAR;
11980             p++;
11981         }
11982         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11983         GameEnds(moveType, p, GE_FILE);
11984         done = TRUE;
11985         if (cmailMsgLoaded) {
11986             ClearHighlights();
11987             flipView = WhiteOnMove(currentMove);
11988             if (moveType == GameUnfinished) flipView = !flipView;
11989             if (appData.debugMode)
11990               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11991         }
11992         break;
11993
11994       case EndOfFile:
11995         if (appData.debugMode)
11996           fprintf(debugFP, "Parser hit end of file\n");
11997         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11998           case MT_NONE:
11999           case MT_CHECK:
12000             break;
12001           case MT_CHECKMATE:
12002           case MT_STAINMATE:
12003             if (WhiteOnMove(currentMove)) {
12004                 GameEnds(BlackWins, "Black mates", GE_FILE);
12005             } else {
12006                 GameEnds(WhiteWins, "White mates", GE_FILE);
12007             }
12008             break;
12009           case MT_STALEMATE:
12010             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12011             break;
12012         }
12013         done = TRUE;
12014         break;
12015
12016       case MoveNumberOne:
12017         if (lastLoadGameStart == GNUChessGame) {
12018             /* GNUChessGames have numbers, but they aren't move numbers */
12019             if (appData.debugMode)
12020               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12021                       yy_text, (int) moveType);
12022             return LoadGameOneMove(EndOfFile); /* tail recursion */
12023         }
12024         /* else fall thru */
12025
12026       case XBoardGame:
12027       case GNUChessGame:
12028       case PGNTag:
12029         /* Reached start of next game in file */
12030         if (appData.debugMode)
12031           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12032         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12033           case MT_NONE:
12034           case MT_CHECK:
12035             break;
12036           case MT_CHECKMATE:
12037           case MT_STAINMATE:
12038             if (WhiteOnMove(currentMove)) {
12039                 GameEnds(BlackWins, "Black mates", GE_FILE);
12040             } else {
12041                 GameEnds(WhiteWins, "White mates", GE_FILE);
12042             }
12043             break;
12044           case MT_STALEMATE:
12045             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12046             break;
12047         }
12048         done = TRUE;
12049         break;
12050
12051       case PositionDiagram:     /* should not happen; ignore */
12052       case ElapsedTime:         /* ignore */
12053       case NAG:                 /* ignore */
12054         if (appData.debugMode)
12055           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12056                   yy_text, (int) moveType);
12057         return LoadGameOneMove(EndOfFile); /* tail recursion */
12058
12059       case IllegalMove:
12060         if (appData.testLegality) {
12061             if (appData.debugMode)
12062               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12063             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12064                     (forwardMostMove / 2) + 1,
12065                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12066             DisplayError(move, 0);
12067             done = TRUE;
12068         } else {
12069             if (appData.debugMode)
12070               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12071                       yy_text, currentMoveString);
12072             fromX = currentMoveString[0] - AAA;
12073             fromY = currentMoveString[1] - ONE;
12074             toX = currentMoveString[2] - AAA;
12075             toY = currentMoveString[3] - ONE;
12076             promoChar = currentMoveString[4];
12077         }
12078         break;
12079
12080       case AmbiguousMove:
12081         if (appData.debugMode)
12082           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12083         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12084                 (forwardMostMove / 2) + 1,
12085                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12086         DisplayError(move, 0);
12087         done = TRUE;
12088         break;
12089
12090       default:
12091       case ImpossibleMove:
12092         if (appData.debugMode)
12093           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12094         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12095                 (forwardMostMove / 2) + 1,
12096                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12097         DisplayError(move, 0);
12098         done = TRUE;
12099         break;
12100     }
12101
12102     if (done) {
12103         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12104             DrawPosition(FALSE, boards[currentMove]);
12105             DisplayBothClocks();
12106             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12107               DisplayComment(currentMove - 1, commentList[currentMove]);
12108         }
12109         (void) StopLoadGameTimer();
12110         gameFileFP = NULL;
12111         cmailOldMove = forwardMostMove;
12112         return FALSE;
12113     } else {
12114         /* currentMoveString is set as a side-effect of yylex */
12115
12116         thinkOutput[0] = NULLCHAR;
12117         MakeMove(fromX, fromY, toX, toY, promoChar);
12118         killX = killY = -1; // [HGM] lion: used up
12119         currentMove = forwardMostMove;
12120         return TRUE;
12121     }
12122 }
12123
12124 /* Load the nth game from the given file */
12125 int
12126 LoadGameFromFile (char *filename, int n, char *title, int useList)
12127 {
12128     FILE *f;
12129     char buf[MSG_SIZ];
12130
12131     if (strcmp(filename, "-") == 0) {
12132         f = stdin;
12133         title = "stdin";
12134     } else {
12135         f = fopen(filename, "rb");
12136         if (f == NULL) {
12137           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12138             DisplayError(buf, errno);
12139             return FALSE;
12140         }
12141     }
12142     if (fseek(f, 0, 0) == -1) {
12143         /* f is not seekable; probably a pipe */
12144         useList = FALSE;
12145     }
12146     if (useList && n == 0) {
12147         int error = GameListBuild(f);
12148         if (error) {
12149             DisplayError(_("Cannot build game list"), error);
12150         } else if (!ListEmpty(&gameList) &&
12151                    ((ListGame *) gameList.tailPred)->number > 1) {
12152             GameListPopUp(f, title);
12153             return TRUE;
12154         }
12155         GameListDestroy();
12156         n = 1;
12157     }
12158     if (n == 0) n = 1;
12159     return LoadGame(f, n, title, FALSE);
12160 }
12161
12162
12163 void
12164 MakeRegisteredMove ()
12165 {
12166     int fromX, fromY, toX, toY;
12167     char promoChar;
12168     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12169         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12170           case CMAIL_MOVE:
12171           case CMAIL_DRAW:
12172             if (appData.debugMode)
12173               fprintf(debugFP, "Restoring %s for game %d\n",
12174                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12175
12176             thinkOutput[0] = NULLCHAR;
12177             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12178             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12179             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12180             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12181             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12182             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12183             MakeMove(fromX, fromY, toX, toY, promoChar);
12184             ShowMove(fromX, fromY, toX, toY);
12185
12186             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12187               case MT_NONE:
12188               case MT_CHECK:
12189                 break;
12190
12191               case MT_CHECKMATE:
12192               case MT_STAINMATE:
12193                 if (WhiteOnMove(currentMove)) {
12194                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12195                 } else {
12196                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12197                 }
12198                 break;
12199
12200               case MT_STALEMATE:
12201                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12202                 break;
12203             }
12204
12205             break;
12206
12207           case CMAIL_RESIGN:
12208             if (WhiteOnMove(currentMove)) {
12209                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12210             } else {
12211                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12212             }
12213             break;
12214
12215           case CMAIL_ACCEPT:
12216             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12217             break;
12218
12219           default:
12220             break;
12221         }
12222     }
12223
12224     return;
12225 }
12226
12227 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12228 int
12229 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12230 {
12231     int retVal;
12232
12233     if (gameNumber > nCmailGames) {
12234         DisplayError(_("No more games in this message"), 0);
12235         return FALSE;
12236     }
12237     if (f == lastLoadGameFP) {
12238         int offset = gameNumber - lastLoadGameNumber;
12239         if (offset == 0) {
12240             cmailMsg[0] = NULLCHAR;
12241             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12242                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12243                 nCmailMovesRegistered--;
12244             }
12245             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12246             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12247                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12248             }
12249         } else {
12250             if (! RegisterMove()) return FALSE;
12251         }
12252     }
12253
12254     retVal = LoadGame(f, gameNumber, title, useList);
12255
12256     /* Make move registered during previous look at this game, if any */
12257     MakeRegisteredMove();
12258
12259     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12260         commentList[currentMove]
12261           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12262         DisplayComment(currentMove - 1, commentList[currentMove]);
12263     }
12264
12265     return retVal;
12266 }
12267
12268 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12269 int
12270 ReloadGame (int offset)
12271 {
12272     int gameNumber = lastLoadGameNumber + offset;
12273     if (lastLoadGameFP == NULL) {
12274         DisplayError(_("No game has been loaded yet"), 0);
12275         return FALSE;
12276     }
12277     if (gameNumber <= 0) {
12278         DisplayError(_("Can't back up any further"), 0);
12279         return FALSE;
12280     }
12281     if (cmailMsgLoaded) {
12282         return CmailLoadGame(lastLoadGameFP, gameNumber,
12283                              lastLoadGameTitle, lastLoadGameUseList);
12284     } else {
12285         return LoadGame(lastLoadGameFP, gameNumber,
12286                         lastLoadGameTitle, lastLoadGameUseList);
12287     }
12288 }
12289
12290 int keys[EmptySquare+1];
12291
12292 int
12293 PositionMatches (Board b1, Board b2)
12294 {
12295     int r, f, sum=0;
12296     switch(appData.searchMode) {
12297         case 1: return CompareWithRights(b1, b2);
12298         case 2:
12299             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12300                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12301             }
12302             return TRUE;
12303         case 3:
12304             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12305               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12306                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12307             }
12308             return sum==0;
12309         case 4:
12310             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12311                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12312             }
12313             return sum==0;
12314     }
12315     return TRUE;
12316 }
12317
12318 #define Q_PROMO  4
12319 #define Q_EP     3
12320 #define Q_BCASTL 2
12321 #define Q_WCASTL 1
12322
12323 int pieceList[256], quickBoard[256];
12324 ChessSquare pieceType[256] = { EmptySquare };
12325 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12326 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12327 int soughtTotal, turn;
12328 Boolean epOK, flipSearch;
12329
12330 typedef struct {
12331     unsigned char piece, to;
12332 } Move;
12333
12334 #define DSIZE (250000)
12335
12336 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12337 Move *moveDatabase = initialSpace;
12338 unsigned int movePtr, dataSize = DSIZE;
12339
12340 int
12341 MakePieceList (Board board, int *counts)
12342 {
12343     int r, f, n=Q_PROMO, total=0;
12344     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12345     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12346         int sq = f + (r<<4);
12347         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12348             quickBoard[sq] = ++n;
12349             pieceList[n] = sq;
12350             pieceType[n] = board[r][f];
12351             counts[board[r][f]]++;
12352             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12353             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12354             total++;
12355         }
12356     }
12357     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12358     return total;
12359 }
12360
12361 void
12362 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12363 {
12364     int sq = fromX + (fromY<<4);
12365     int piece = quickBoard[sq], rook;
12366     quickBoard[sq] = 0;
12367     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12368     if(piece == pieceList[1] && fromY == toY) {
12369       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12370         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12371         moveDatabase[movePtr++].piece = Q_WCASTL;
12372         quickBoard[sq] = piece;
12373         piece = quickBoard[from]; quickBoard[from] = 0;
12374         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12375       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12376         quickBoard[sq] = 0; // remove Rook
12377         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12378         moveDatabase[movePtr++].piece = Q_WCASTL;
12379         quickBoard[sq] = pieceList[1]; // put King
12380         piece = rook;
12381         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12382       }
12383     } else
12384     if(piece == pieceList[2] && fromY == toY) {
12385       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12386         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12387         moveDatabase[movePtr++].piece = Q_BCASTL;
12388         quickBoard[sq] = piece;
12389         piece = quickBoard[from]; quickBoard[from] = 0;
12390         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12391       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12392         quickBoard[sq] = 0; // remove Rook
12393         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12394         moveDatabase[movePtr++].piece = Q_BCASTL;
12395         quickBoard[sq] = pieceList[2]; // put King
12396         piece = rook;
12397         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12398       }
12399     } else
12400     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12401         quickBoard[(fromY<<4)+toX] = 0;
12402         moveDatabase[movePtr].piece = Q_EP;
12403         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12404         moveDatabase[movePtr].to = sq;
12405     } else
12406     if(promoPiece != pieceType[piece]) {
12407         moveDatabase[movePtr++].piece = Q_PROMO;
12408         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12409     }
12410     moveDatabase[movePtr].piece = piece;
12411     quickBoard[sq] = piece;
12412     movePtr++;
12413 }
12414
12415 int
12416 PackGame (Board board)
12417 {
12418     Move *newSpace = NULL;
12419     moveDatabase[movePtr].piece = 0; // terminate previous game
12420     if(movePtr > dataSize) {
12421         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12422         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12423         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12424         if(newSpace) {
12425             int i;
12426             Move *p = moveDatabase, *q = newSpace;
12427             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12428             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12429             moveDatabase = newSpace;
12430         } else { // calloc failed, we must be out of memory. Too bad...
12431             dataSize = 0; // prevent calloc events for all subsequent games
12432             return 0;     // and signal this one isn't cached
12433         }
12434     }
12435     movePtr++;
12436     MakePieceList(board, counts);
12437     return movePtr;
12438 }
12439
12440 int
12441 QuickCompare (Board board, int *minCounts, int *maxCounts)
12442 {   // compare according to search mode
12443     int r, f;
12444     switch(appData.searchMode)
12445     {
12446       case 1: // exact position match
12447         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12448         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12449             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12450         }
12451         break;
12452       case 2: // can have extra material on empty squares
12453         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12454             if(board[r][f] == EmptySquare) continue;
12455             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12456         }
12457         break;
12458       case 3: // material with exact Pawn structure
12459         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12460             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12461             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12462         } // fall through to material comparison
12463       case 4: // exact material
12464         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12465         break;
12466       case 6: // material range with given imbalance
12467         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12468         // fall through to range comparison
12469       case 5: // material range
12470         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12471     }
12472     return TRUE;
12473 }
12474
12475 int
12476 QuickScan (Board board, Move *move)
12477 {   // reconstruct game,and compare all positions in it
12478     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12479     do {
12480         int piece = move->piece;
12481         int to = move->to, from = pieceList[piece];
12482         if(found < 0) { // if already found just scan to game end for final piece count
12483           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12484            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12485            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12486                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12487             ) {
12488             static int lastCounts[EmptySquare+1];
12489             int i;
12490             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12491             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12492           } else stretch = 0;
12493           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12494           if(found >= 0 && !appData.minPieces) return found;
12495         }
12496         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12497           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12498           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12499             piece = (++move)->piece;
12500             from = pieceList[piece];
12501             counts[pieceType[piece]]--;
12502             pieceType[piece] = (ChessSquare) move->to;
12503             counts[move->to]++;
12504           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12505             counts[pieceType[quickBoard[to]]]--;
12506             quickBoard[to] = 0; total--;
12507             move++;
12508             continue;
12509           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12510             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12511             from  = pieceList[piece]; // so this must be King
12512             quickBoard[from] = 0;
12513             pieceList[piece] = to;
12514             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12515             quickBoard[from] = 0; // rook
12516             quickBoard[to] = piece;
12517             to = move->to; piece = move->piece;
12518             goto aftercastle;
12519           }
12520         }
12521         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12522         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12523         quickBoard[from] = 0;
12524       aftercastle:
12525         quickBoard[to] = piece;
12526         pieceList[piece] = to;
12527         cnt++; turn ^= 3;
12528         move++;
12529     } while(1);
12530 }
12531
12532 void
12533 InitSearch ()
12534 {
12535     int r, f;
12536     flipSearch = FALSE;
12537     CopyBoard(soughtBoard, boards[currentMove]);
12538     soughtTotal = MakePieceList(soughtBoard, maxSought);
12539     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12540     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12541     CopyBoard(reverseBoard, boards[currentMove]);
12542     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12543         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12544         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12545         reverseBoard[r][f] = piece;
12546     }
12547     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12548     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12549     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12550                  || (boards[currentMove][CASTLING][2] == NoRights ||
12551                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12552                  && (boards[currentMove][CASTLING][5] == NoRights ||
12553                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12554       ) {
12555         flipSearch = TRUE;
12556         CopyBoard(flipBoard, soughtBoard);
12557         CopyBoard(rotateBoard, reverseBoard);
12558         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12559             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12560             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12561         }
12562     }
12563     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12564     if(appData.searchMode >= 5) {
12565         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12566         MakePieceList(soughtBoard, minSought);
12567         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12568     }
12569     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12570         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12571 }
12572
12573 GameInfo dummyInfo;
12574 static int creatingBook;
12575
12576 int
12577 GameContainsPosition (FILE *f, ListGame *lg)
12578 {
12579     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12580     int fromX, fromY, toX, toY;
12581     char promoChar;
12582     static int initDone=FALSE;
12583
12584     // weed out games based on numerical tag comparison
12585     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12586     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12587     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12588     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12589     if(!initDone) {
12590         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12591         initDone = TRUE;
12592     }
12593     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12594     else CopyBoard(boards[scratch], initialPosition); // default start position
12595     if(lg->moves) {
12596         turn = btm + 1;
12597         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12598         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12599     }
12600     if(btm) plyNr++;
12601     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12602     fseek(f, lg->offset, 0);
12603     yynewfile(f);
12604     while(1) {
12605         yyboardindex = scratch;
12606         quickFlag = plyNr+1;
12607         next = Myylex();
12608         quickFlag = 0;
12609         switch(next) {
12610             case PGNTag:
12611                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12612             default:
12613                 continue;
12614
12615             case XBoardGame:
12616             case GNUChessGame:
12617                 if(plyNr) return -1; // after we have seen moves, this is for new game
12618               continue;
12619
12620             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12621             case ImpossibleMove:
12622             case WhiteWins: // game ends here with these four
12623             case BlackWins:
12624             case GameIsDrawn:
12625             case GameUnfinished:
12626                 return -1;
12627
12628             case IllegalMove:
12629                 if(appData.testLegality) return -1;
12630             case WhiteCapturesEnPassant:
12631             case BlackCapturesEnPassant:
12632             case WhitePromotion:
12633             case BlackPromotion:
12634             case WhiteNonPromotion:
12635             case BlackNonPromotion:
12636             case NormalMove:
12637             case FirstLeg:
12638             case WhiteKingSideCastle:
12639             case WhiteQueenSideCastle:
12640             case BlackKingSideCastle:
12641             case BlackQueenSideCastle:
12642             case WhiteKingSideCastleWild:
12643             case WhiteQueenSideCastleWild:
12644             case BlackKingSideCastleWild:
12645             case BlackQueenSideCastleWild:
12646             case WhiteHSideCastleFR:
12647             case WhiteASideCastleFR:
12648             case BlackHSideCastleFR:
12649             case BlackASideCastleFR:
12650                 fromX = currentMoveString[0] - AAA;
12651                 fromY = currentMoveString[1] - ONE;
12652                 toX = currentMoveString[2] - AAA;
12653                 toY = currentMoveString[3] - ONE;
12654                 promoChar = currentMoveString[4];
12655                 break;
12656             case WhiteDrop:
12657             case BlackDrop:
12658                 fromX = next == WhiteDrop ?
12659                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12660                   (int) CharToPiece(ToLower(currentMoveString[0]));
12661                 fromY = DROP_RANK;
12662                 toX = currentMoveString[2] - AAA;
12663                 toY = currentMoveString[3] - ONE;
12664                 promoChar = 0;
12665                 break;
12666         }
12667         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12668         plyNr++;
12669         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12670         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12671         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12672         if(appData.findMirror) {
12673             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12674             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12675         }
12676     }
12677 }
12678
12679 /* Load the nth game from open file f */
12680 int
12681 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12682 {
12683     ChessMove cm;
12684     char buf[MSG_SIZ];
12685     int gn = gameNumber;
12686     ListGame *lg = NULL;
12687     int numPGNTags = 0;
12688     int err, pos = -1;
12689     GameMode oldGameMode;
12690     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12691
12692     if (appData.debugMode)
12693         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12694
12695     if (gameMode == Training )
12696         SetTrainingModeOff();
12697
12698     oldGameMode = gameMode;
12699     if (gameMode != BeginningOfGame) {
12700       Reset(FALSE, TRUE);
12701     }
12702     killX = killY = -1; // [HGM] lion: in case we did not Reset
12703
12704     gameFileFP = f;
12705     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12706         fclose(lastLoadGameFP);
12707     }
12708
12709     if (useList) {
12710         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12711
12712         if (lg) {
12713             fseek(f, lg->offset, 0);
12714             GameListHighlight(gameNumber);
12715             pos = lg->position;
12716             gn = 1;
12717         }
12718         else {
12719             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12720               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12721             else
12722             DisplayError(_("Game number out of range"), 0);
12723             return FALSE;
12724         }
12725     } else {
12726         GameListDestroy();
12727         if (fseek(f, 0, 0) == -1) {
12728             if (f == lastLoadGameFP ?
12729                 gameNumber == lastLoadGameNumber + 1 :
12730                 gameNumber == 1) {
12731                 gn = 1;
12732             } else {
12733                 DisplayError(_("Can't seek on game file"), 0);
12734                 return FALSE;
12735             }
12736         }
12737     }
12738     lastLoadGameFP = f;
12739     lastLoadGameNumber = gameNumber;
12740     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12741     lastLoadGameUseList = useList;
12742
12743     yynewfile(f);
12744
12745     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12746       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12747                 lg->gameInfo.black);
12748             DisplayTitle(buf);
12749     } else if (*title != NULLCHAR) {
12750         if (gameNumber > 1) {
12751           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12752             DisplayTitle(buf);
12753         } else {
12754             DisplayTitle(title);
12755         }
12756     }
12757
12758     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12759         gameMode = PlayFromGameFile;
12760         ModeHighlight();
12761     }
12762
12763     currentMove = forwardMostMove = backwardMostMove = 0;
12764     CopyBoard(boards[0], initialPosition);
12765     StopClocks();
12766
12767     /*
12768      * Skip the first gn-1 games in the file.
12769      * Also skip over anything that precedes an identifiable
12770      * start of game marker, to avoid being confused by
12771      * garbage at the start of the file.  Currently
12772      * recognized start of game markers are the move number "1",
12773      * the pattern "gnuchess .* game", the pattern
12774      * "^[#;%] [^ ]* game file", and a PGN tag block.
12775      * A game that starts with one of the latter two patterns
12776      * will also have a move number 1, possibly
12777      * following a position diagram.
12778      * 5-4-02: Let's try being more lenient and allowing a game to
12779      * start with an unnumbered move.  Does that break anything?
12780      */
12781     cm = lastLoadGameStart = EndOfFile;
12782     while (gn > 0) {
12783         yyboardindex = forwardMostMove;
12784         cm = (ChessMove) Myylex();
12785         switch (cm) {
12786           case EndOfFile:
12787             if (cmailMsgLoaded) {
12788                 nCmailGames = CMAIL_MAX_GAMES - gn;
12789             } else {
12790                 Reset(TRUE, TRUE);
12791                 DisplayError(_("Game not found in file"), 0);
12792             }
12793             return FALSE;
12794
12795           case GNUChessGame:
12796           case XBoardGame:
12797             gn--;
12798             lastLoadGameStart = cm;
12799             break;
12800
12801           case MoveNumberOne:
12802             switch (lastLoadGameStart) {
12803               case GNUChessGame:
12804               case XBoardGame:
12805               case PGNTag:
12806                 break;
12807               case MoveNumberOne:
12808               case EndOfFile:
12809                 gn--;           /* count this game */
12810                 lastLoadGameStart = cm;
12811                 break;
12812               default:
12813                 /* impossible */
12814                 break;
12815             }
12816             break;
12817
12818           case PGNTag:
12819             switch (lastLoadGameStart) {
12820               case GNUChessGame:
12821               case PGNTag:
12822               case MoveNumberOne:
12823               case EndOfFile:
12824                 gn--;           /* count this game */
12825                 lastLoadGameStart = cm;
12826                 break;
12827               case XBoardGame:
12828                 lastLoadGameStart = cm; /* game counted already */
12829                 break;
12830               default:
12831                 /* impossible */
12832                 break;
12833             }
12834             if (gn > 0) {
12835                 do {
12836                     yyboardindex = forwardMostMove;
12837                     cm = (ChessMove) Myylex();
12838                 } while (cm == PGNTag || cm == Comment);
12839             }
12840             break;
12841
12842           case WhiteWins:
12843           case BlackWins:
12844           case GameIsDrawn:
12845             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12846                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12847                     != CMAIL_OLD_RESULT) {
12848                     nCmailResults ++ ;
12849                     cmailResult[  CMAIL_MAX_GAMES
12850                                 - gn - 1] = CMAIL_OLD_RESULT;
12851                 }
12852             }
12853             break;
12854
12855           case NormalMove:
12856           case FirstLeg:
12857             /* Only a NormalMove can be at the start of a game
12858              * without a position diagram. */
12859             if (lastLoadGameStart == EndOfFile ) {
12860               gn--;
12861               lastLoadGameStart = MoveNumberOne;
12862             }
12863             break;
12864
12865           default:
12866             break;
12867         }
12868     }
12869
12870     if (appData.debugMode)
12871       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12872
12873     if (cm == XBoardGame) {
12874         /* Skip any header junk before position diagram and/or move 1 */
12875         for (;;) {
12876             yyboardindex = forwardMostMove;
12877             cm = (ChessMove) Myylex();
12878
12879             if (cm == EndOfFile ||
12880                 cm == GNUChessGame || cm == XBoardGame) {
12881                 /* Empty game; pretend end-of-file and handle later */
12882                 cm = EndOfFile;
12883                 break;
12884             }
12885
12886             if (cm == MoveNumberOne || cm == PositionDiagram ||
12887                 cm == PGNTag || cm == Comment)
12888               break;
12889         }
12890     } else if (cm == GNUChessGame) {
12891         if (gameInfo.event != NULL) {
12892             free(gameInfo.event);
12893         }
12894         gameInfo.event = StrSave(yy_text);
12895     }
12896
12897     startedFromSetupPosition = FALSE;
12898     while (cm == PGNTag) {
12899         if (appData.debugMode)
12900           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12901         err = ParsePGNTag(yy_text, &gameInfo);
12902         if (!err) numPGNTags++;
12903
12904         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12905         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
12906             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12907             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12908             InitPosition(TRUE);
12909             oldVariant = gameInfo.variant;
12910             if (appData.debugMode)
12911               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12912         }
12913
12914
12915         if (gameInfo.fen != NULL) {
12916           Board initial_position;
12917           startedFromSetupPosition = TRUE;
12918           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12919             Reset(TRUE, TRUE);
12920             DisplayError(_("Bad FEN position in file"), 0);
12921             return FALSE;
12922           }
12923           CopyBoard(boards[0], initial_position);
12924           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
12925             CopyBoard(initialPosition, initial_position);
12926           if (blackPlaysFirst) {
12927             currentMove = forwardMostMove = backwardMostMove = 1;
12928             CopyBoard(boards[1], initial_position);
12929             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12930             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12931             timeRemaining[0][1] = whiteTimeRemaining;
12932             timeRemaining[1][1] = blackTimeRemaining;
12933             if (commentList[0] != NULL) {
12934               commentList[1] = commentList[0];
12935               commentList[0] = NULL;
12936             }
12937           } else {
12938             currentMove = forwardMostMove = backwardMostMove = 0;
12939           }
12940           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12941           {   int i;
12942               initialRulePlies = FENrulePlies;
12943               for( i=0; i< nrCastlingRights; i++ )
12944                   initialRights[i] = initial_position[CASTLING][i];
12945           }
12946           yyboardindex = forwardMostMove;
12947           free(gameInfo.fen);
12948           gameInfo.fen = NULL;
12949         }
12950
12951         yyboardindex = forwardMostMove;
12952         cm = (ChessMove) Myylex();
12953
12954         /* Handle comments interspersed among the tags */
12955         while (cm == Comment) {
12956             char *p;
12957             if (appData.debugMode)
12958               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12959             p = yy_text;
12960             AppendComment(currentMove, p, FALSE);
12961             yyboardindex = forwardMostMove;
12962             cm = (ChessMove) Myylex();
12963         }
12964     }
12965
12966     /* don't rely on existence of Event tag since if game was
12967      * pasted from clipboard the Event tag may not exist
12968      */
12969     if (numPGNTags > 0){
12970         char *tags;
12971         if (gameInfo.variant == VariantNormal) {
12972           VariantClass v = StringToVariant(gameInfo.event);
12973           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12974           if(v < VariantShogi) gameInfo.variant = v;
12975         }
12976         if (!matchMode) {
12977           if( appData.autoDisplayTags ) {
12978             tags = PGNTags(&gameInfo);
12979             TagsPopUp(tags, CmailMsg());
12980             free(tags);
12981           }
12982         }
12983     } else {
12984         /* Make something up, but don't display it now */
12985         SetGameInfo();
12986         TagsPopDown();
12987     }
12988
12989     if (cm == PositionDiagram) {
12990         int i, j;
12991         char *p;
12992         Board initial_position;
12993
12994         if (appData.debugMode)
12995           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12996
12997         if (!startedFromSetupPosition) {
12998             p = yy_text;
12999             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13000               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13001                 switch (*p) {
13002                   case '{':
13003                   case '[':
13004                   case '-':
13005                   case ' ':
13006                   case '\t':
13007                   case '\n':
13008                   case '\r':
13009                     break;
13010                   default:
13011                     initial_position[i][j++] = CharToPiece(*p);
13012                     break;
13013                 }
13014             while (*p == ' ' || *p == '\t' ||
13015                    *p == '\n' || *p == '\r') p++;
13016
13017             if (strncmp(p, "black", strlen("black"))==0)
13018               blackPlaysFirst = TRUE;
13019             else
13020               blackPlaysFirst = FALSE;
13021             startedFromSetupPosition = TRUE;
13022
13023             CopyBoard(boards[0], initial_position);
13024             if (blackPlaysFirst) {
13025                 currentMove = forwardMostMove = backwardMostMove = 1;
13026                 CopyBoard(boards[1], initial_position);
13027                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13028                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13029                 timeRemaining[0][1] = whiteTimeRemaining;
13030                 timeRemaining[1][1] = blackTimeRemaining;
13031                 if (commentList[0] != NULL) {
13032                     commentList[1] = commentList[0];
13033                     commentList[0] = NULL;
13034                 }
13035             } else {
13036                 currentMove = forwardMostMove = backwardMostMove = 0;
13037             }
13038         }
13039         yyboardindex = forwardMostMove;
13040         cm = (ChessMove) Myylex();
13041     }
13042
13043   if(!creatingBook) {
13044     if (first.pr == NoProc) {
13045         StartChessProgram(&first);
13046     }
13047     InitChessProgram(&first, FALSE);
13048     SendToProgram("force\n", &first);
13049     if (startedFromSetupPosition) {
13050         SendBoard(&first, forwardMostMove);
13051     if (appData.debugMode) {
13052         fprintf(debugFP, "Load Game\n");
13053     }
13054         DisplayBothClocks();
13055     }
13056   }
13057
13058     /* [HGM] server: flag to write setup moves in broadcast file as one */
13059     loadFlag = appData.suppressLoadMoves;
13060
13061     while (cm == Comment) {
13062         char *p;
13063         if (appData.debugMode)
13064           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13065         p = yy_text;
13066         AppendComment(currentMove, p, FALSE);
13067         yyboardindex = forwardMostMove;
13068         cm = (ChessMove) Myylex();
13069     }
13070
13071     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13072         cm == WhiteWins || cm == BlackWins ||
13073         cm == GameIsDrawn || cm == GameUnfinished) {
13074         DisplayMessage("", _("No moves in game"));
13075         if (cmailMsgLoaded) {
13076             if (appData.debugMode)
13077               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13078             ClearHighlights();
13079             flipView = FALSE;
13080         }
13081         DrawPosition(FALSE, boards[currentMove]);
13082         DisplayBothClocks();
13083         gameMode = EditGame;
13084         ModeHighlight();
13085         gameFileFP = NULL;
13086         cmailOldMove = 0;
13087         return TRUE;
13088     }
13089
13090     // [HGM] PV info: routine tests if comment empty
13091     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13092         DisplayComment(currentMove - 1, commentList[currentMove]);
13093     }
13094     if (!matchMode && appData.timeDelay != 0)
13095       DrawPosition(FALSE, boards[currentMove]);
13096
13097     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13098       programStats.ok_to_send = 1;
13099     }
13100
13101     /* if the first token after the PGN tags is a move
13102      * and not move number 1, retrieve it from the parser
13103      */
13104     if (cm != MoveNumberOne)
13105         LoadGameOneMove(cm);
13106
13107     /* load the remaining moves from the file */
13108     while (LoadGameOneMove(EndOfFile)) {
13109       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13110       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13111     }
13112
13113     /* rewind to the start of the game */
13114     currentMove = backwardMostMove;
13115
13116     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13117
13118     if (oldGameMode == AnalyzeFile) {
13119       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13120       AnalyzeFileEvent();
13121     } else
13122     if (oldGameMode == AnalyzeMode) {
13123       AnalyzeFileEvent();
13124     }
13125
13126     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13127         long int w, b; // [HGM] adjourn: restore saved clock times
13128         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13129         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13130             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13131             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13132         }
13133     }
13134
13135     if(creatingBook) return TRUE;
13136     if (!matchMode && pos > 0) {
13137         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13138     } else
13139     if (matchMode || appData.timeDelay == 0) {
13140       ToEndEvent();
13141     } else if (appData.timeDelay > 0) {
13142       AutoPlayGameLoop();
13143     }
13144
13145     if (appData.debugMode)
13146         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13147
13148     loadFlag = 0; /* [HGM] true game starts */
13149     return TRUE;
13150 }
13151
13152 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13153 int
13154 ReloadPosition (int offset)
13155 {
13156     int positionNumber = lastLoadPositionNumber + offset;
13157     if (lastLoadPositionFP == NULL) {
13158         DisplayError(_("No position has been loaded yet"), 0);
13159         return FALSE;
13160     }
13161     if (positionNumber <= 0) {
13162         DisplayError(_("Can't back up any further"), 0);
13163         return FALSE;
13164     }
13165     return LoadPosition(lastLoadPositionFP, positionNumber,
13166                         lastLoadPositionTitle);
13167 }
13168
13169 /* Load the nth position from the given file */
13170 int
13171 LoadPositionFromFile (char *filename, int n, char *title)
13172 {
13173     FILE *f;
13174     char buf[MSG_SIZ];
13175
13176     if (strcmp(filename, "-") == 0) {
13177         return LoadPosition(stdin, n, "stdin");
13178     } else {
13179         f = fopen(filename, "rb");
13180         if (f == NULL) {
13181             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13182             DisplayError(buf, errno);
13183             return FALSE;
13184         } else {
13185             return LoadPosition(f, n, title);
13186         }
13187     }
13188 }
13189
13190 /* Load the nth position from the given open file, and close it */
13191 int
13192 LoadPosition (FILE *f, int positionNumber, char *title)
13193 {
13194     char *p, line[MSG_SIZ];
13195     Board initial_position;
13196     int i, j, fenMode, pn;
13197
13198     if (gameMode == Training )
13199         SetTrainingModeOff();
13200
13201     if (gameMode != BeginningOfGame) {
13202         Reset(FALSE, TRUE);
13203     }
13204     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13205         fclose(lastLoadPositionFP);
13206     }
13207     if (positionNumber == 0) positionNumber = 1;
13208     lastLoadPositionFP = f;
13209     lastLoadPositionNumber = positionNumber;
13210     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13211     if (first.pr == NoProc && !appData.noChessProgram) {
13212       StartChessProgram(&first);
13213       InitChessProgram(&first, FALSE);
13214     }
13215     pn = positionNumber;
13216     if (positionNumber < 0) {
13217         /* Negative position number means to seek to that byte offset */
13218         if (fseek(f, -positionNumber, 0) == -1) {
13219             DisplayError(_("Can't seek on position file"), 0);
13220             return FALSE;
13221         };
13222         pn = 1;
13223     } else {
13224         if (fseek(f, 0, 0) == -1) {
13225             if (f == lastLoadPositionFP ?
13226                 positionNumber == lastLoadPositionNumber + 1 :
13227                 positionNumber == 1) {
13228                 pn = 1;
13229             } else {
13230                 DisplayError(_("Can't seek on position file"), 0);
13231                 return FALSE;
13232             }
13233         }
13234     }
13235     /* See if this file is FEN or old-style xboard */
13236     if (fgets(line, MSG_SIZ, f) == NULL) {
13237         DisplayError(_("Position not found in file"), 0);
13238         return FALSE;
13239     }
13240     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13241     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13242
13243     if (pn >= 2) {
13244         if (fenMode || line[0] == '#') pn--;
13245         while (pn > 0) {
13246             /* skip positions before number pn */
13247             if (fgets(line, MSG_SIZ, f) == NULL) {
13248                 Reset(TRUE, TRUE);
13249                 DisplayError(_("Position not found in file"), 0);
13250                 return FALSE;
13251             }
13252             if (fenMode || line[0] == '#') pn--;
13253         }
13254     }
13255
13256     if (fenMode) {
13257         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13258             DisplayError(_("Bad FEN position in file"), 0);
13259             return FALSE;
13260         }
13261     } else {
13262         (void) fgets(line, MSG_SIZ, f);
13263         (void) fgets(line, MSG_SIZ, f);
13264
13265         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13266             (void) fgets(line, MSG_SIZ, f);
13267             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13268                 if (*p == ' ')
13269                   continue;
13270                 initial_position[i][j++] = CharToPiece(*p);
13271             }
13272         }
13273
13274         blackPlaysFirst = FALSE;
13275         if (!feof(f)) {
13276             (void) fgets(line, MSG_SIZ, f);
13277             if (strncmp(line, "black", strlen("black"))==0)
13278               blackPlaysFirst = TRUE;
13279         }
13280     }
13281     startedFromSetupPosition = TRUE;
13282
13283     CopyBoard(boards[0], initial_position);
13284     if (blackPlaysFirst) {
13285         currentMove = forwardMostMove = backwardMostMove = 1;
13286         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13287         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13288         CopyBoard(boards[1], initial_position);
13289         DisplayMessage("", _("Black to play"));
13290     } else {
13291         currentMove = forwardMostMove = backwardMostMove = 0;
13292         DisplayMessage("", _("White to play"));
13293     }
13294     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13295     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13296         SendToProgram("force\n", &first);
13297         SendBoard(&first, forwardMostMove);
13298     }
13299     if (appData.debugMode) {
13300 int i, j;
13301   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13302   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13303         fprintf(debugFP, "Load Position\n");
13304     }
13305
13306     if (positionNumber > 1) {
13307       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13308         DisplayTitle(line);
13309     } else {
13310         DisplayTitle(title);
13311     }
13312     gameMode = EditGame;
13313     ModeHighlight();
13314     ResetClocks();
13315     timeRemaining[0][1] = whiteTimeRemaining;
13316     timeRemaining[1][1] = blackTimeRemaining;
13317     DrawPosition(FALSE, boards[currentMove]);
13318
13319     return TRUE;
13320 }
13321
13322
13323 void
13324 CopyPlayerNameIntoFileName (char **dest, char *src)
13325 {
13326     while (*src != NULLCHAR && *src != ',') {
13327         if (*src == ' ') {
13328             *(*dest)++ = '_';
13329             src++;
13330         } else {
13331             *(*dest)++ = *src++;
13332         }
13333     }
13334 }
13335
13336 char *
13337 DefaultFileName (char *ext)
13338 {
13339     static char def[MSG_SIZ];
13340     char *p;
13341
13342     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13343         p = def;
13344         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13345         *p++ = '-';
13346         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13347         *p++ = '.';
13348         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13349     } else {
13350         def[0] = NULLCHAR;
13351     }
13352     return def;
13353 }
13354
13355 /* Save the current game to the given file */
13356 int
13357 SaveGameToFile (char *filename, int append)
13358 {
13359     FILE *f;
13360     char buf[MSG_SIZ];
13361     int result, i, t,tot=0;
13362
13363     if (strcmp(filename, "-") == 0) {
13364         return SaveGame(stdout, 0, NULL);
13365     } else {
13366         for(i=0; i<10; i++) { // upto 10 tries
13367              f = fopen(filename, append ? "a" : "w");
13368              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13369              if(f || errno != 13) break;
13370              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13371              tot += t;
13372         }
13373         if (f == NULL) {
13374             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13375             DisplayError(buf, errno);
13376             return FALSE;
13377         } else {
13378             safeStrCpy(buf, lastMsg, MSG_SIZ);
13379             DisplayMessage(_("Waiting for access to save file"), "");
13380             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13381             DisplayMessage(_("Saving game"), "");
13382             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13383             result = SaveGame(f, 0, NULL);
13384             DisplayMessage(buf, "");
13385             return result;
13386         }
13387     }
13388 }
13389
13390 char *
13391 SavePart (char *str)
13392 {
13393     static char buf[MSG_SIZ];
13394     char *p;
13395
13396     p = strchr(str, ' ');
13397     if (p == NULL) return str;
13398     strncpy(buf, str, p - str);
13399     buf[p - str] = NULLCHAR;
13400     return buf;
13401 }
13402
13403 #define PGN_MAX_LINE 75
13404
13405 #define PGN_SIDE_WHITE  0
13406 #define PGN_SIDE_BLACK  1
13407
13408 static int
13409 FindFirstMoveOutOfBook (int side)
13410 {
13411     int result = -1;
13412
13413     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13414         int index = backwardMostMove;
13415         int has_book_hit = 0;
13416
13417         if( (index % 2) != side ) {
13418             index++;
13419         }
13420
13421         while( index < forwardMostMove ) {
13422             /* Check to see if engine is in book */
13423             int depth = pvInfoList[index].depth;
13424             int score = pvInfoList[index].score;
13425             int in_book = 0;
13426
13427             if( depth <= 2 ) {
13428                 in_book = 1;
13429             }
13430             else if( score == 0 && depth == 63 ) {
13431                 in_book = 1; /* Zappa */
13432             }
13433             else if( score == 2 && depth == 99 ) {
13434                 in_book = 1; /* Abrok */
13435             }
13436
13437             has_book_hit += in_book;
13438
13439             if( ! in_book ) {
13440                 result = index;
13441
13442                 break;
13443             }
13444
13445             index += 2;
13446         }
13447     }
13448
13449     return result;
13450 }
13451
13452 void
13453 GetOutOfBookInfo (char * buf)
13454 {
13455     int oob[2];
13456     int i;
13457     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13458
13459     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13460     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13461
13462     *buf = '\0';
13463
13464     if( oob[0] >= 0 || oob[1] >= 0 ) {
13465         for( i=0; i<2; i++ ) {
13466             int idx = oob[i];
13467
13468             if( idx >= 0 ) {
13469                 if( i > 0 && oob[0] >= 0 ) {
13470                     strcat( buf, "   " );
13471                 }
13472
13473                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13474                 sprintf( buf+strlen(buf), "%s%.2f",
13475                     pvInfoList[idx].score >= 0 ? "+" : "",
13476                     pvInfoList[idx].score / 100.0 );
13477             }
13478         }
13479     }
13480 }
13481
13482 /* Save game in PGN style */
13483 static void
13484 SaveGamePGN2 (FILE *f)
13485 {
13486     int i, offset, linelen, newblock;
13487 //    char *movetext;
13488     char numtext[32];
13489     int movelen, numlen, blank;
13490     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13491
13492     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13493
13494     PrintPGNTags(f, &gameInfo);
13495
13496     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13497
13498     if (backwardMostMove > 0 || startedFromSetupPosition) {
13499         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13500         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13501         fprintf(f, "\n{--------------\n");
13502         PrintPosition(f, backwardMostMove);
13503         fprintf(f, "--------------}\n");
13504         free(fen);
13505     }
13506     else {
13507         /* [AS] Out of book annotation */
13508         if( appData.saveOutOfBookInfo ) {
13509             char buf[64];
13510
13511             GetOutOfBookInfo( buf );
13512
13513             if( buf[0] != '\0' ) {
13514                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13515             }
13516         }
13517
13518         fprintf(f, "\n");
13519     }
13520
13521     i = backwardMostMove;
13522     linelen = 0;
13523     newblock = TRUE;
13524
13525     while (i < forwardMostMove) {
13526         /* Print comments preceding this move */
13527         if (commentList[i] != NULL) {
13528             if (linelen > 0) fprintf(f, "\n");
13529             fprintf(f, "%s", commentList[i]);
13530             linelen = 0;
13531             newblock = TRUE;
13532         }
13533
13534         /* Format move number */
13535         if ((i % 2) == 0)
13536           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13537         else
13538           if (newblock)
13539             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13540           else
13541             numtext[0] = NULLCHAR;
13542
13543         numlen = strlen(numtext);
13544         newblock = FALSE;
13545
13546         /* Print move number */
13547         blank = linelen > 0 && numlen > 0;
13548         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13549             fprintf(f, "\n");
13550             linelen = 0;
13551             blank = 0;
13552         }
13553         if (blank) {
13554             fprintf(f, " ");
13555             linelen++;
13556         }
13557         fprintf(f, "%s", numtext);
13558         linelen += numlen;
13559
13560         /* Get move */
13561         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13562         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13563
13564         /* Print move */
13565         blank = linelen > 0 && movelen > 0;
13566         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13567             fprintf(f, "\n");
13568             linelen = 0;
13569             blank = 0;
13570         }
13571         if (blank) {
13572             fprintf(f, " ");
13573             linelen++;
13574         }
13575         fprintf(f, "%s", move_buffer);
13576         linelen += movelen;
13577
13578         /* [AS] Add PV info if present */
13579         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13580             /* [HGM] add time */
13581             char buf[MSG_SIZ]; int seconds;
13582
13583             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13584
13585             if( seconds <= 0)
13586               buf[0] = 0;
13587             else
13588               if( seconds < 30 )
13589                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13590               else
13591                 {
13592                   seconds = (seconds + 4)/10; // round to full seconds
13593                   if( seconds < 60 )
13594                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13595                   else
13596                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13597                 }
13598
13599             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13600                       pvInfoList[i].score >= 0 ? "+" : "",
13601                       pvInfoList[i].score / 100.0,
13602                       pvInfoList[i].depth,
13603                       buf );
13604
13605             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13606
13607             /* Print score/depth */
13608             blank = linelen > 0 && movelen > 0;
13609             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13610                 fprintf(f, "\n");
13611                 linelen = 0;
13612                 blank = 0;
13613             }
13614             if (blank) {
13615                 fprintf(f, " ");
13616                 linelen++;
13617             }
13618             fprintf(f, "%s", move_buffer);
13619             linelen += movelen;
13620         }
13621
13622         i++;
13623     }
13624
13625     /* Start a new line */
13626     if (linelen > 0) fprintf(f, "\n");
13627
13628     /* Print comments after last move */
13629     if (commentList[i] != NULL) {
13630         fprintf(f, "%s\n", commentList[i]);
13631     }
13632
13633     /* Print result */
13634     if (gameInfo.resultDetails != NULL &&
13635         gameInfo.resultDetails[0] != NULLCHAR) {
13636         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13637         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13638            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13639             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13640         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13641     } else {
13642         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13643     }
13644 }
13645
13646 /* Save game in PGN style and close the file */
13647 int
13648 SaveGamePGN (FILE *f)
13649 {
13650     SaveGamePGN2(f);
13651     fclose(f);
13652     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13653     return TRUE;
13654 }
13655
13656 /* Save game in old style and close the file */
13657 int
13658 SaveGameOldStyle (FILE *f)
13659 {
13660     int i, offset;
13661     time_t tm;
13662
13663     tm = time((time_t *) NULL);
13664
13665     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13666     PrintOpponents(f);
13667
13668     if (backwardMostMove > 0 || startedFromSetupPosition) {
13669         fprintf(f, "\n[--------------\n");
13670         PrintPosition(f, backwardMostMove);
13671         fprintf(f, "--------------]\n");
13672     } else {
13673         fprintf(f, "\n");
13674     }
13675
13676     i = backwardMostMove;
13677     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13678
13679     while (i < forwardMostMove) {
13680         if (commentList[i] != NULL) {
13681             fprintf(f, "[%s]\n", commentList[i]);
13682         }
13683
13684         if ((i % 2) == 1) {
13685             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13686             i++;
13687         } else {
13688             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13689             i++;
13690             if (commentList[i] != NULL) {
13691                 fprintf(f, "\n");
13692                 continue;
13693             }
13694             if (i >= forwardMostMove) {
13695                 fprintf(f, "\n");
13696                 break;
13697             }
13698             fprintf(f, "%s\n", parseList[i]);
13699             i++;
13700         }
13701     }
13702
13703     if (commentList[i] != NULL) {
13704         fprintf(f, "[%s]\n", commentList[i]);
13705     }
13706
13707     /* This isn't really the old style, but it's close enough */
13708     if (gameInfo.resultDetails != NULL &&
13709         gameInfo.resultDetails[0] != NULLCHAR) {
13710         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13711                 gameInfo.resultDetails);
13712     } else {
13713         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13714     }
13715
13716     fclose(f);
13717     return TRUE;
13718 }
13719
13720 /* Save the current game to open file f and close the file */
13721 int
13722 SaveGame (FILE *f, int dummy, char *dummy2)
13723 {
13724     if (gameMode == EditPosition) EditPositionDone(TRUE);
13725     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13726     if (appData.oldSaveStyle)
13727       return SaveGameOldStyle(f);
13728     else
13729       return SaveGamePGN(f);
13730 }
13731
13732 /* Save the current position to the given file */
13733 int
13734 SavePositionToFile (char *filename)
13735 {
13736     FILE *f;
13737     char buf[MSG_SIZ];
13738
13739     if (strcmp(filename, "-") == 0) {
13740         return SavePosition(stdout, 0, NULL);
13741     } else {
13742         f = fopen(filename, "a");
13743         if (f == NULL) {
13744             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13745             DisplayError(buf, errno);
13746             return FALSE;
13747         } else {
13748             safeStrCpy(buf, lastMsg, MSG_SIZ);
13749             DisplayMessage(_("Waiting for access to save file"), "");
13750             flock(fileno(f), LOCK_EX); // [HGM] lock
13751             DisplayMessage(_("Saving position"), "");
13752             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13753             SavePosition(f, 0, NULL);
13754             DisplayMessage(buf, "");
13755             return TRUE;
13756         }
13757     }
13758 }
13759
13760 /* Save the current position to the given open file and close the file */
13761 int
13762 SavePosition (FILE *f, int dummy, char *dummy2)
13763 {
13764     time_t tm;
13765     char *fen;
13766
13767     if (gameMode == EditPosition) EditPositionDone(TRUE);
13768     if (appData.oldSaveStyle) {
13769         tm = time((time_t *) NULL);
13770
13771         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13772         PrintOpponents(f);
13773         fprintf(f, "[--------------\n");
13774         PrintPosition(f, currentMove);
13775         fprintf(f, "--------------]\n");
13776     } else {
13777         fen = PositionToFEN(currentMove, NULL, 1);
13778         fprintf(f, "%s\n", fen);
13779         free(fen);
13780     }
13781     fclose(f);
13782     return TRUE;
13783 }
13784
13785 void
13786 ReloadCmailMsgEvent (int unregister)
13787 {
13788 #if !WIN32
13789     static char *inFilename = NULL;
13790     static char *outFilename;
13791     int i;
13792     struct stat inbuf, outbuf;
13793     int status;
13794
13795     /* Any registered moves are unregistered if unregister is set, */
13796     /* i.e. invoked by the signal handler */
13797     if (unregister) {
13798         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13799             cmailMoveRegistered[i] = FALSE;
13800             if (cmailCommentList[i] != NULL) {
13801                 free(cmailCommentList[i]);
13802                 cmailCommentList[i] = NULL;
13803             }
13804         }
13805         nCmailMovesRegistered = 0;
13806     }
13807
13808     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13809         cmailResult[i] = CMAIL_NOT_RESULT;
13810     }
13811     nCmailResults = 0;
13812
13813     if (inFilename == NULL) {
13814         /* Because the filenames are static they only get malloced once  */
13815         /* and they never get freed                                      */
13816         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13817         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13818
13819         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13820         sprintf(outFilename, "%s.out", appData.cmailGameName);
13821     }
13822
13823     status = stat(outFilename, &outbuf);
13824     if (status < 0) {
13825         cmailMailedMove = FALSE;
13826     } else {
13827         status = stat(inFilename, &inbuf);
13828         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13829     }
13830
13831     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13832        counts the games, notes how each one terminated, etc.
13833
13834        It would be nice to remove this kludge and instead gather all
13835        the information while building the game list.  (And to keep it
13836        in the game list nodes instead of having a bunch of fixed-size
13837        parallel arrays.)  Note this will require getting each game's
13838        termination from the PGN tags, as the game list builder does
13839        not process the game moves.  --mann
13840        */
13841     cmailMsgLoaded = TRUE;
13842     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13843
13844     /* Load first game in the file or popup game menu */
13845     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13846
13847 #endif /* !WIN32 */
13848     return;
13849 }
13850
13851 int
13852 RegisterMove ()
13853 {
13854     FILE *f;
13855     char string[MSG_SIZ];
13856
13857     if (   cmailMailedMove
13858         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13859         return TRUE;            /* Allow free viewing  */
13860     }
13861
13862     /* Unregister move to ensure that we don't leave RegisterMove        */
13863     /* with the move registered when the conditions for registering no   */
13864     /* longer hold                                                       */
13865     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13866         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13867         nCmailMovesRegistered --;
13868
13869         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13870           {
13871               free(cmailCommentList[lastLoadGameNumber - 1]);
13872               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13873           }
13874     }
13875
13876     if (cmailOldMove == -1) {
13877         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13878         return FALSE;
13879     }
13880
13881     if (currentMove > cmailOldMove + 1) {
13882         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13883         return FALSE;
13884     }
13885
13886     if (currentMove < cmailOldMove) {
13887         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13888         return FALSE;
13889     }
13890
13891     if (forwardMostMove > currentMove) {
13892         /* Silently truncate extra moves */
13893         TruncateGame();
13894     }
13895
13896     if (   (currentMove == cmailOldMove + 1)
13897         || (   (currentMove == cmailOldMove)
13898             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13899                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13900         if (gameInfo.result != GameUnfinished) {
13901             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13902         }
13903
13904         if (commentList[currentMove] != NULL) {
13905             cmailCommentList[lastLoadGameNumber - 1]
13906               = StrSave(commentList[currentMove]);
13907         }
13908         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13909
13910         if (appData.debugMode)
13911           fprintf(debugFP, "Saving %s for game %d\n",
13912                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13913
13914         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13915
13916         f = fopen(string, "w");
13917         if (appData.oldSaveStyle) {
13918             SaveGameOldStyle(f); /* also closes the file */
13919
13920             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13921             f = fopen(string, "w");
13922             SavePosition(f, 0, NULL); /* also closes the file */
13923         } else {
13924             fprintf(f, "{--------------\n");
13925             PrintPosition(f, currentMove);
13926             fprintf(f, "--------------}\n\n");
13927
13928             SaveGame(f, 0, NULL); /* also closes the file*/
13929         }
13930
13931         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13932         nCmailMovesRegistered ++;
13933     } else if (nCmailGames == 1) {
13934         DisplayError(_("You have not made a move yet"), 0);
13935         return FALSE;
13936     }
13937
13938     return TRUE;
13939 }
13940
13941 void
13942 MailMoveEvent ()
13943 {
13944 #if !WIN32
13945     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13946     FILE *commandOutput;
13947     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13948     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13949     int nBuffers;
13950     int i;
13951     int archived;
13952     char *arcDir;
13953
13954     if (! cmailMsgLoaded) {
13955         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13956         return;
13957     }
13958
13959     if (nCmailGames == nCmailResults) {
13960         DisplayError(_("No unfinished games"), 0);
13961         return;
13962     }
13963
13964 #if CMAIL_PROHIBIT_REMAIL
13965     if (cmailMailedMove) {
13966       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);
13967         DisplayError(msg, 0);
13968         return;
13969     }
13970 #endif
13971
13972     if (! (cmailMailedMove || RegisterMove())) return;
13973
13974     if (   cmailMailedMove
13975         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13976       snprintf(string, MSG_SIZ, partCommandString,
13977                appData.debugMode ? " -v" : "", appData.cmailGameName);
13978         commandOutput = popen(string, "r");
13979
13980         if (commandOutput == NULL) {
13981             DisplayError(_("Failed to invoke cmail"), 0);
13982         } else {
13983             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13984                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13985             }
13986             if (nBuffers > 1) {
13987                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13988                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13989                 nBytes = MSG_SIZ - 1;
13990             } else {
13991                 (void) memcpy(msg, buffer, nBytes);
13992             }
13993             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13994
13995             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13996                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13997
13998                 archived = TRUE;
13999                 for (i = 0; i < nCmailGames; i ++) {
14000                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14001                         archived = FALSE;
14002                     }
14003                 }
14004                 if (   archived
14005                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14006                         != NULL)) {
14007                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14008                            arcDir,
14009                            appData.cmailGameName,
14010                            gameInfo.date);
14011                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14012                     cmailMsgLoaded = FALSE;
14013                 }
14014             }
14015
14016             DisplayInformation(msg);
14017             pclose(commandOutput);
14018         }
14019     } else {
14020         if ((*cmailMsg) != '\0') {
14021             DisplayInformation(cmailMsg);
14022         }
14023     }
14024
14025     return;
14026 #endif /* !WIN32 */
14027 }
14028
14029 char *
14030 CmailMsg ()
14031 {
14032 #if WIN32
14033     return NULL;
14034 #else
14035     int  prependComma = 0;
14036     char number[5];
14037     char string[MSG_SIZ];       /* Space for game-list */
14038     int  i;
14039
14040     if (!cmailMsgLoaded) return "";
14041
14042     if (cmailMailedMove) {
14043       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14044     } else {
14045         /* Create a list of games left */
14046       snprintf(string, MSG_SIZ, "[");
14047         for (i = 0; i < nCmailGames; i ++) {
14048             if (! (   cmailMoveRegistered[i]
14049                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14050                 if (prependComma) {
14051                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14052                 } else {
14053                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14054                     prependComma = 1;
14055                 }
14056
14057                 strcat(string, number);
14058             }
14059         }
14060         strcat(string, "]");
14061
14062         if (nCmailMovesRegistered + nCmailResults == 0) {
14063             switch (nCmailGames) {
14064               case 1:
14065                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14066                 break;
14067
14068               case 2:
14069                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14070                 break;
14071
14072               default:
14073                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14074                          nCmailGames);
14075                 break;
14076             }
14077         } else {
14078             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14079               case 1:
14080                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14081                          string);
14082                 break;
14083
14084               case 0:
14085                 if (nCmailResults == nCmailGames) {
14086                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14087                 } else {
14088                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14089                 }
14090                 break;
14091
14092               default:
14093                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14094                          string);
14095             }
14096         }
14097     }
14098     return cmailMsg;
14099 #endif /* WIN32 */
14100 }
14101
14102 void
14103 ResetGameEvent ()
14104 {
14105     if (gameMode == Training)
14106       SetTrainingModeOff();
14107
14108     Reset(TRUE, TRUE);
14109     cmailMsgLoaded = FALSE;
14110     if (appData.icsActive) {
14111       SendToICS(ics_prefix);
14112       SendToICS("refresh\n");
14113     }
14114 }
14115
14116 void
14117 ExitEvent (int status)
14118 {
14119     exiting++;
14120     if (exiting > 2) {
14121       /* Give up on clean exit */
14122       exit(status);
14123     }
14124     if (exiting > 1) {
14125       /* Keep trying for clean exit */
14126       return;
14127     }
14128
14129     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14130     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14131
14132     if (telnetISR != NULL) {
14133       RemoveInputSource(telnetISR);
14134     }
14135     if (icsPR != NoProc) {
14136       DestroyChildProcess(icsPR, TRUE);
14137     }
14138
14139     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14140     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14141
14142     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14143     /* make sure this other one finishes before killing it!                  */
14144     if(endingGame) { int count = 0;
14145         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14146         while(endingGame && count++ < 10) DoSleep(1);
14147         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14148     }
14149
14150     /* Kill off chess programs */
14151     if (first.pr != NoProc) {
14152         ExitAnalyzeMode();
14153
14154         DoSleep( appData.delayBeforeQuit );
14155         SendToProgram("quit\n", &first);
14156         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14157     }
14158     if (second.pr != NoProc) {
14159         DoSleep( appData.delayBeforeQuit );
14160         SendToProgram("quit\n", &second);
14161         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14162     }
14163     if (first.isr != NULL) {
14164         RemoveInputSource(first.isr);
14165     }
14166     if (second.isr != NULL) {
14167         RemoveInputSource(second.isr);
14168     }
14169
14170     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14171     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14172
14173     ShutDownFrontEnd();
14174     exit(status);
14175 }
14176
14177 void
14178 PauseEngine (ChessProgramState *cps)
14179 {
14180     SendToProgram("pause\n", cps);
14181     cps->pause = 2;
14182 }
14183
14184 void
14185 UnPauseEngine (ChessProgramState *cps)
14186 {
14187     SendToProgram("resume\n", cps);
14188     cps->pause = 1;
14189 }
14190
14191 void
14192 PauseEvent ()
14193 {
14194     if (appData.debugMode)
14195         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14196     if (pausing) {
14197         pausing = FALSE;
14198         ModeHighlight();
14199         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14200             StartClocks();
14201             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14202                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14203                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14204             }
14205             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14206             HandleMachineMove(stashedInputMove, stalledEngine);
14207             stalledEngine = NULL;
14208             return;
14209         }
14210         if (gameMode == MachinePlaysWhite ||
14211             gameMode == TwoMachinesPlay   ||
14212             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14213             if(first.pause)  UnPauseEngine(&first);
14214             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14215             if(second.pause) UnPauseEngine(&second);
14216             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14217             StartClocks();
14218         } else {
14219             DisplayBothClocks();
14220         }
14221         if (gameMode == PlayFromGameFile) {
14222             if (appData.timeDelay >= 0)
14223                 AutoPlayGameLoop();
14224         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14225             Reset(FALSE, TRUE);
14226             SendToICS(ics_prefix);
14227             SendToICS("refresh\n");
14228         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14229             ForwardInner(forwardMostMove);
14230         }
14231         pauseExamInvalid = FALSE;
14232     } else {
14233         switch (gameMode) {
14234           default:
14235             return;
14236           case IcsExamining:
14237             pauseExamForwardMostMove = forwardMostMove;
14238             pauseExamInvalid = FALSE;
14239             /* fall through */
14240           case IcsObserving:
14241           case IcsPlayingWhite:
14242           case IcsPlayingBlack:
14243             pausing = TRUE;
14244             ModeHighlight();
14245             return;
14246           case PlayFromGameFile:
14247             (void) StopLoadGameTimer();
14248             pausing = TRUE;
14249             ModeHighlight();
14250             break;
14251           case BeginningOfGame:
14252             if (appData.icsActive) return;
14253             /* else fall through */
14254           case MachinePlaysWhite:
14255           case MachinePlaysBlack:
14256           case TwoMachinesPlay:
14257             if (forwardMostMove == 0)
14258               return;           /* don't pause if no one has moved */
14259             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14260                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14261                 if(onMove->pause) {           // thinking engine can be paused
14262                     PauseEngine(onMove);      // do it
14263                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14264                         PauseEngine(onMove->other);
14265                     else
14266                         SendToProgram("easy\n", onMove->other);
14267                     StopClocks();
14268                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14269             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14270                 if(first.pause) {
14271                     PauseEngine(&first);
14272                     StopClocks();
14273                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14274             } else { // human on move, pause pondering by either method
14275                 if(first.pause)
14276                     PauseEngine(&first);
14277                 else if(appData.ponderNextMove)
14278                     SendToProgram("easy\n", &first);
14279                 StopClocks();
14280             }
14281             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14282           case AnalyzeMode:
14283             pausing = TRUE;
14284             ModeHighlight();
14285             break;
14286         }
14287     }
14288 }
14289
14290 void
14291 EditCommentEvent ()
14292 {
14293     char title[MSG_SIZ];
14294
14295     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14296       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14297     } else {
14298       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14299                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14300                parseList[currentMove - 1]);
14301     }
14302
14303     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14304 }
14305
14306
14307 void
14308 EditTagsEvent ()
14309 {
14310     char *tags = PGNTags(&gameInfo);
14311     bookUp = FALSE;
14312     EditTagsPopUp(tags, NULL);
14313     free(tags);
14314 }
14315
14316 void
14317 ToggleSecond ()
14318 {
14319   if(second.analyzing) {
14320     SendToProgram("exit\n", &second);
14321     second.analyzing = FALSE;
14322   } else {
14323     if (second.pr == NoProc) StartChessProgram(&second);
14324     InitChessProgram(&second, FALSE);
14325     FeedMovesToProgram(&second, currentMove);
14326
14327     SendToProgram("analyze\n", &second);
14328     second.analyzing = TRUE;
14329   }
14330 }
14331
14332 /* Toggle ShowThinking */
14333 void
14334 ToggleShowThinking()
14335 {
14336   appData.showThinking = !appData.showThinking;
14337   ShowThinkingEvent();
14338 }
14339
14340 int
14341 AnalyzeModeEvent ()
14342 {
14343     char buf[MSG_SIZ];
14344
14345     if (!first.analysisSupport) {
14346       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14347       DisplayError(buf, 0);
14348       return 0;
14349     }
14350     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14351     if (appData.icsActive) {
14352         if (gameMode != IcsObserving) {
14353           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14354             DisplayError(buf, 0);
14355             /* secure check */
14356             if (appData.icsEngineAnalyze) {
14357                 if (appData.debugMode)
14358                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14359                 ExitAnalyzeMode();
14360                 ModeHighlight();
14361             }
14362             return 0;
14363         }
14364         /* if enable, user wants to disable icsEngineAnalyze */
14365         if (appData.icsEngineAnalyze) {
14366                 ExitAnalyzeMode();
14367                 ModeHighlight();
14368                 return 0;
14369         }
14370         appData.icsEngineAnalyze = TRUE;
14371         if (appData.debugMode)
14372             fprintf(debugFP, "ICS engine analyze starting... \n");
14373     }
14374
14375     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14376     if (appData.noChessProgram || gameMode == AnalyzeMode)
14377       return 0;
14378
14379     if (gameMode != AnalyzeFile) {
14380         if (!appData.icsEngineAnalyze) {
14381                EditGameEvent();
14382                if (gameMode != EditGame) return 0;
14383         }
14384         if (!appData.showThinking) ToggleShowThinking();
14385         ResurrectChessProgram();
14386         SendToProgram("analyze\n", &first);
14387         first.analyzing = TRUE;
14388         /*first.maybeThinking = TRUE;*/
14389         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14390         EngineOutputPopUp();
14391     }
14392     if (!appData.icsEngineAnalyze) {
14393         gameMode = AnalyzeMode;
14394         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14395     }
14396     pausing = FALSE;
14397     ModeHighlight();
14398     SetGameInfo();
14399
14400     StartAnalysisClock();
14401     GetTimeMark(&lastNodeCountTime);
14402     lastNodeCount = 0;
14403     return 1;
14404 }
14405
14406 void
14407 AnalyzeFileEvent ()
14408 {
14409     if (appData.noChessProgram || gameMode == AnalyzeFile)
14410       return;
14411
14412     if (!first.analysisSupport) {
14413       char buf[MSG_SIZ];
14414       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14415       DisplayError(buf, 0);
14416       return;
14417     }
14418
14419     if (gameMode != AnalyzeMode) {
14420         keepInfo = 1; // mere annotating should not alter PGN tags
14421         EditGameEvent();
14422         keepInfo = 0;
14423         if (gameMode != EditGame) return;
14424         if (!appData.showThinking) ToggleShowThinking();
14425         ResurrectChessProgram();
14426         SendToProgram("analyze\n", &first);
14427         first.analyzing = TRUE;
14428         /*first.maybeThinking = TRUE;*/
14429         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14430         EngineOutputPopUp();
14431     }
14432     gameMode = AnalyzeFile;
14433     pausing = FALSE;
14434     ModeHighlight();
14435
14436     StartAnalysisClock();
14437     GetTimeMark(&lastNodeCountTime);
14438     lastNodeCount = 0;
14439     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14440     AnalysisPeriodicEvent(1);
14441 }
14442
14443 void
14444 MachineWhiteEvent ()
14445 {
14446     char buf[MSG_SIZ];
14447     char *bookHit = NULL;
14448
14449     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14450       return;
14451
14452
14453     if (gameMode == PlayFromGameFile ||
14454         gameMode == TwoMachinesPlay  ||
14455         gameMode == Training         ||
14456         gameMode == AnalyzeMode      ||
14457         gameMode == EndOfGame)
14458         EditGameEvent();
14459
14460     if (gameMode == EditPosition)
14461         EditPositionDone(TRUE);
14462
14463     if (!WhiteOnMove(currentMove)) {
14464         DisplayError(_("It is not White's turn"), 0);
14465         return;
14466     }
14467
14468     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14469       ExitAnalyzeMode();
14470
14471     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14472         gameMode == AnalyzeFile)
14473         TruncateGame();
14474
14475     ResurrectChessProgram();    /* in case it isn't running */
14476     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14477         gameMode = MachinePlaysWhite;
14478         ResetClocks();
14479     } else
14480     gameMode = MachinePlaysWhite;
14481     pausing = FALSE;
14482     ModeHighlight();
14483     SetGameInfo();
14484     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14485     DisplayTitle(buf);
14486     if (first.sendName) {
14487       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14488       SendToProgram(buf, &first);
14489     }
14490     if (first.sendTime) {
14491       if (first.useColors) {
14492         SendToProgram("black\n", &first); /*gnu kludge*/
14493       }
14494       SendTimeRemaining(&first, TRUE);
14495     }
14496     if (first.useColors) {
14497       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14498     }
14499     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14500     SetMachineThinkingEnables();
14501     first.maybeThinking = TRUE;
14502     StartClocks();
14503     firstMove = FALSE;
14504
14505     if (appData.autoFlipView && !flipView) {
14506       flipView = !flipView;
14507       DrawPosition(FALSE, NULL);
14508       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14509     }
14510
14511     if(bookHit) { // [HGM] book: simulate book reply
14512         static char bookMove[MSG_SIZ]; // a bit generous?
14513
14514         programStats.nodes = programStats.depth = programStats.time =
14515         programStats.score = programStats.got_only_move = 0;
14516         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14517
14518         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14519         strcat(bookMove, bookHit);
14520         HandleMachineMove(bookMove, &first);
14521     }
14522 }
14523
14524 void
14525 MachineBlackEvent ()
14526 {
14527   char buf[MSG_SIZ];
14528   char *bookHit = NULL;
14529
14530     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14531         return;
14532
14533
14534     if (gameMode == PlayFromGameFile ||
14535         gameMode == TwoMachinesPlay  ||
14536         gameMode == Training         ||
14537         gameMode == AnalyzeMode      ||
14538         gameMode == EndOfGame)
14539         EditGameEvent();
14540
14541     if (gameMode == EditPosition)
14542         EditPositionDone(TRUE);
14543
14544     if (WhiteOnMove(currentMove)) {
14545         DisplayError(_("It is not Black's turn"), 0);
14546         return;
14547     }
14548
14549     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14550       ExitAnalyzeMode();
14551
14552     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14553         gameMode == AnalyzeFile)
14554         TruncateGame();
14555
14556     ResurrectChessProgram();    /* in case it isn't running */
14557     gameMode = MachinePlaysBlack;
14558     pausing = FALSE;
14559     ModeHighlight();
14560     SetGameInfo();
14561     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14562     DisplayTitle(buf);
14563     if (first.sendName) {
14564       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14565       SendToProgram(buf, &first);
14566     }
14567     if (first.sendTime) {
14568       if (first.useColors) {
14569         SendToProgram("white\n", &first); /*gnu kludge*/
14570       }
14571       SendTimeRemaining(&first, FALSE);
14572     }
14573     if (first.useColors) {
14574       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14575     }
14576     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14577     SetMachineThinkingEnables();
14578     first.maybeThinking = TRUE;
14579     StartClocks();
14580
14581     if (appData.autoFlipView && flipView) {
14582       flipView = !flipView;
14583       DrawPosition(FALSE, NULL);
14584       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14585     }
14586     if(bookHit) { // [HGM] book: simulate book reply
14587         static char bookMove[MSG_SIZ]; // a bit generous?
14588
14589         programStats.nodes = programStats.depth = programStats.time =
14590         programStats.score = programStats.got_only_move = 0;
14591         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14592
14593         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14594         strcat(bookMove, bookHit);
14595         HandleMachineMove(bookMove, &first);
14596     }
14597 }
14598
14599
14600 void
14601 DisplayTwoMachinesTitle ()
14602 {
14603     char buf[MSG_SIZ];
14604     if (appData.matchGames > 0) {
14605         if(appData.tourneyFile[0]) {
14606           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14607                    gameInfo.white, _("vs."), gameInfo.black,
14608                    nextGame+1, appData.matchGames+1,
14609                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14610         } else
14611         if (first.twoMachinesColor[0] == 'w') {
14612           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14613                    gameInfo.white, _("vs."),  gameInfo.black,
14614                    first.matchWins, second.matchWins,
14615                    matchGame - 1 - (first.matchWins + second.matchWins));
14616         } else {
14617           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14618                    gameInfo.white, _("vs."), gameInfo.black,
14619                    second.matchWins, first.matchWins,
14620                    matchGame - 1 - (first.matchWins + second.matchWins));
14621         }
14622     } else {
14623       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14624     }
14625     DisplayTitle(buf);
14626 }
14627
14628 void
14629 SettingsMenuIfReady ()
14630 {
14631   if (second.lastPing != second.lastPong) {
14632     DisplayMessage("", _("Waiting for second chess program"));
14633     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14634     return;
14635   }
14636   ThawUI();
14637   DisplayMessage("", "");
14638   SettingsPopUp(&second);
14639 }
14640
14641 int
14642 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14643 {
14644     char buf[MSG_SIZ];
14645     if (cps->pr == NoProc) {
14646         StartChessProgram(cps);
14647         if (cps->protocolVersion == 1) {
14648           retry();
14649           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14650         } else {
14651           /* kludge: allow timeout for initial "feature" command */
14652           if(retry != TwoMachinesEventIfReady) FreezeUI();
14653           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14654           DisplayMessage("", buf);
14655           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14656         }
14657         return 1;
14658     }
14659     return 0;
14660 }
14661
14662 void
14663 TwoMachinesEvent P((void))
14664 {
14665     int i;
14666     char buf[MSG_SIZ];
14667     ChessProgramState *onmove;
14668     char *bookHit = NULL;
14669     static int stalling = 0;
14670     TimeMark now;
14671     long wait;
14672
14673     if (appData.noChessProgram) return;
14674
14675     switch (gameMode) {
14676       case TwoMachinesPlay:
14677         return;
14678       case MachinePlaysWhite:
14679       case MachinePlaysBlack:
14680         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14681             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14682             return;
14683         }
14684         /* fall through */
14685       case BeginningOfGame:
14686       case PlayFromGameFile:
14687       case EndOfGame:
14688         EditGameEvent();
14689         if (gameMode != EditGame) return;
14690         break;
14691       case EditPosition:
14692         EditPositionDone(TRUE);
14693         break;
14694       case AnalyzeMode:
14695       case AnalyzeFile:
14696         ExitAnalyzeMode();
14697         break;
14698       case EditGame:
14699       default:
14700         break;
14701     }
14702
14703 //    forwardMostMove = currentMove;
14704     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14705     startingEngine = TRUE;
14706
14707     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14708
14709     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14710     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14711       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14712       return;
14713     }
14714     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14715
14716     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14717                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14718         startingEngine = matchMode = FALSE;
14719         DisplayError("second engine does not play this", 0);
14720         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14721         EditGameEvent(); // switch back to EditGame mode
14722         return;
14723     }
14724
14725     if(!stalling) {
14726       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14727       SendToProgram("force\n", &second);
14728       stalling = 1;
14729       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14730       return;
14731     }
14732     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14733     if(appData.matchPause>10000 || appData.matchPause<10)
14734                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14735     wait = SubtractTimeMarks(&now, &pauseStart);
14736     if(wait < appData.matchPause) {
14737         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14738         return;
14739     }
14740     // we are now committed to starting the game
14741     stalling = 0;
14742     DisplayMessage("", "");
14743     if (startedFromSetupPosition) {
14744         SendBoard(&second, backwardMostMove);
14745     if (appData.debugMode) {
14746         fprintf(debugFP, "Two Machines\n");
14747     }
14748     }
14749     for (i = backwardMostMove; i < forwardMostMove; i++) {
14750         SendMoveToProgram(i, &second);
14751     }
14752
14753     gameMode = TwoMachinesPlay;
14754     pausing = startingEngine = FALSE;
14755     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14756     SetGameInfo();
14757     DisplayTwoMachinesTitle();
14758     firstMove = TRUE;
14759     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14760         onmove = &first;
14761     } else {
14762         onmove = &second;
14763     }
14764     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14765     SendToProgram(first.computerString, &first);
14766     if (first.sendName) {
14767       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14768       SendToProgram(buf, &first);
14769     }
14770     SendToProgram(second.computerString, &second);
14771     if (second.sendName) {
14772       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14773       SendToProgram(buf, &second);
14774     }
14775
14776     ResetClocks();
14777     if (!first.sendTime || !second.sendTime) {
14778         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14779         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14780     }
14781     if (onmove->sendTime) {
14782       if (onmove->useColors) {
14783         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14784       }
14785       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14786     }
14787     if (onmove->useColors) {
14788       SendToProgram(onmove->twoMachinesColor, onmove);
14789     }
14790     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14791 //    SendToProgram("go\n", onmove);
14792     onmove->maybeThinking = TRUE;
14793     SetMachineThinkingEnables();
14794
14795     StartClocks();
14796
14797     if(bookHit) { // [HGM] book: simulate book reply
14798         static char bookMove[MSG_SIZ]; // a bit generous?
14799
14800         programStats.nodes = programStats.depth = programStats.time =
14801         programStats.score = programStats.got_only_move = 0;
14802         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14803
14804         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14805         strcat(bookMove, bookHit);
14806         savedMessage = bookMove; // args for deferred call
14807         savedState = onmove;
14808         ScheduleDelayedEvent(DeferredBookMove, 1);
14809     }
14810 }
14811
14812 void
14813 TrainingEvent ()
14814 {
14815     if (gameMode == Training) {
14816       SetTrainingModeOff();
14817       gameMode = PlayFromGameFile;
14818       DisplayMessage("", _("Training mode off"));
14819     } else {
14820       gameMode = Training;
14821       animateTraining = appData.animate;
14822
14823       /* make sure we are not already at the end of the game */
14824       if (currentMove < forwardMostMove) {
14825         SetTrainingModeOn();
14826         DisplayMessage("", _("Training mode on"));
14827       } else {
14828         gameMode = PlayFromGameFile;
14829         DisplayError(_("Already at end of game"), 0);
14830       }
14831     }
14832     ModeHighlight();
14833 }
14834
14835 void
14836 IcsClientEvent ()
14837 {
14838     if (!appData.icsActive) return;
14839     switch (gameMode) {
14840       case IcsPlayingWhite:
14841       case IcsPlayingBlack:
14842       case IcsObserving:
14843       case IcsIdle:
14844       case BeginningOfGame:
14845       case IcsExamining:
14846         return;
14847
14848       case EditGame:
14849         break;
14850
14851       case EditPosition:
14852         EditPositionDone(TRUE);
14853         break;
14854
14855       case AnalyzeMode:
14856       case AnalyzeFile:
14857         ExitAnalyzeMode();
14858         break;
14859
14860       default:
14861         EditGameEvent();
14862         break;
14863     }
14864
14865     gameMode = IcsIdle;
14866     ModeHighlight();
14867     return;
14868 }
14869
14870 void
14871 EditGameEvent ()
14872 {
14873     int i;
14874
14875     switch (gameMode) {
14876       case Training:
14877         SetTrainingModeOff();
14878         break;
14879       case MachinePlaysWhite:
14880       case MachinePlaysBlack:
14881       case BeginningOfGame:
14882         SendToProgram("force\n", &first);
14883         SetUserThinkingEnables();
14884         break;
14885       case PlayFromGameFile:
14886         (void) StopLoadGameTimer();
14887         if (gameFileFP != NULL) {
14888             gameFileFP = NULL;
14889         }
14890         break;
14891       case EditPosition:
14892         EditPositionDone(TRUE);
14893         break;
14894       case AnalyzeMode:
14895       case AnalyzeFile:
14896         ExitAnalyzeMode();
14897         SendToProgram("force\n", &first);
14898         break;
14899       case TwoMachinesPlay:
14900         GameEnds(EndOfFile, NULL, GE_PLAYER);
14901         ResurrectChessProgram();
14902         SetUserThinkingEnables();
14903         break;
14904       case EndOfGame:
14905         ResurrectChessProgram();
14906         break;
14907       case IcsPlayingBlack:
14908       case IcsPlayingWhite:
14909         DisplayError(_("Warning: You are still playing a game"), 0);
14910         break;
14911       case IcsObserving:
14912         DisplayError(_("Warning: You are still observing a game"), 0);
14913         break;
14914       case IcsExamining:
14915         DisplayError(_("Warning: You are still examining a game"), 0);
14916         break;
14917       case IcsIdle:
14918         break;
14919       case EditGame:
14920       default:
14921         return;
14922     }
14923
14924     pausing = FALSE;
14925     StopClocks();
14926     first.offeredDraw = second.offeredDraw = 0;
14927
14928     if (gameMode == PlayFromGameFile) {
14929         whiteTimeRemaining = timeRemaining[0][currentMove];
14930         blackTimeRemaining = timeRemaining[1][currentMove];
14931         DisplayTitle("");
14932     }
14933
14934     if (gameMode == MachinePlaysWhite ||
14935         gameMode == MachinePlaysBlack ||
14936         gameMode == TwoMachinesPlay ||
14937         gameMode == EndOfGame) {
14938         i = forwardMostMove;
14939         while (i > currentMove) {
14940             SendToProgram("undo\n", &first);
14941             i--;
14942         }
14943         if(!adjustedClock) {
14944         whiteTimeRemaining = timeRemaining[0][currentMove];
14945         blackTimeRemaining = timeRemaining[1][currentMove];
14946         DisplayBothClocks();
14947         }
14948         if (whiteFlag || blackFlag) {
14949             whiteFlag = blackFlag = 0;
14950         }
14951         DisplayTitle("");
14952     }
14953
14954     gameMode = EditGame;
14955     ModeHighlight();
14956     SetGameInfo();
14957 }
14958
14959
14960 void
14961 EditPositionEvent ()
14962 {
14963     if (gameMode == EditPosition) {
14964         EditGameEvent();
14965         return;
14966     }
14967
14968     EditGameEvent();
14969     if (gameMode != EditGame) return;
14970
14971     gameMode = EditPosition;
14972     ModeHighlight();
14973     SetGameInfo();
14974     if (currentMove > 0)
14975       CopyBoard(boards[0], boards[currentMove]);
14976
14977     blackPlaysFirst = !WhiteOnMove(currentMove);
14978     ResetClocks();
14979     currentMove = forwardMostMove = backwardMostMove = 0;
14980     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14981     DisplayMove(-1);
14982     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14983 }
14984
14985 void
14986 ExitAnalyzeMode ()
14987 {
14988     /* [DM] icsEngineAnalyze - possible call from other functions */
14989     if (appData.icsEngineAnalyze) {
14990         appData.icsEngineAnalyze = FALSE;
14991
14992         DisplayMessage("",_("Close ICS engine analyze..."));
14993     }
14994     if (first.analysisSupport && first.analyzing) {
14995       SendToBoth("exit\n");
14996       first.analyzing = second.analyzing = FALSE;
14997     }
14998     thinkOutput[0] = NULLCHAR;
14999 }
15000
15001 void
15002 EditPositionDone (Boolean fakeRights)
15003 {
15004     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15005
15006     startedFromSetupPosition = TRUE;
15007     InitChessProgram(&first, FALSE);
15008     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15009       boards[0][EP_STATUS] = EP_NONE;
15010       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15011       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15012         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15013         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15014       } else boards[0][CASTLING][2] = NoRights;
15015       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15016         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15017         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15018       } else boards[0][CASTLING][5] = NoRights;
15019       if(gameInfo.variant == VariantSChess) {
15020         int i;
15021         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15022           boards[0][VIRGIN][i] = 0;
15023           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15024           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15025         }
15026       }
15027     }
15028     SendToProgram("force\n", &first);
15029     if (blackPlaysFirst) {
15030         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15031         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15032         currentMove = forwardMostMove = backwardMostMove = 1;
15033         CopyBoard(boards[1], boards[0]);
15034     } else {
15035         currentMove = forwardMostMove = backwardMostMove = 0;
15036     }
15037     SendBoard(&first, forwardMostMove);
15038     if (appData.debugMode) {
15039         fprintf(debugFP, "EditPosDone\n");
15040     }
15041     DisplayTitle("");
15042     DisplayMessage("", "");
15043     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15044     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15045     gameMode = EditGame;
15046     ModeHighlight();
15047     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15048     ClearHighlights(); /* [AS] */
15049 }
15050
15051 /* Pause for `ms' milliseconds */
15052 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15053 void
15054 TimeDelay (long ms)
15055 {
15056     TimeMark m1, m2;
15057
15058     GetTimeMark(&m1);
15059     do {
15060         GetTimeMark(&m2);
15061     } while (SubtractTimeMarks(&m2, &m1) < ms);
15062 }
15063
15064 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15065 void
15066 SendMultiLineToICS (char *buf)
15067 {
15068     char temp[MSG_SIZ+1], *p;
15069     int len;
15070
15071     len = strlen(buf);
15072     if (len > MSG_SIZ)
15073       len = MSG_SIZ;
15074
15075     strncpy(temp, buf, len);
15076     temp[len] = 0;
15077
15078     p = temp;
15079     while (*p) {
15080         if (*p == '\n' || *p == '\r')
15081           *p = ' ';
15082         ++p;
15083     }
15084
15085     strcat(temp, "\n");
15086     SendToICS(temp);
15087     SendToPlayer(temp, strlen(temp));
15088 }
15089
15090 void
15091 SetWhiteToPlayEvent ()
15092 {
15093     if (gameMode == EditPosition) {
15094         blackPlaysFirst = FALSE;
15095         DisplayBothClocks();    /* works because currentMove is 0 */
15096     } else if (gameMode == IcsExamining) {
15097         SendToICS(ics_prefix);
15098         SendToICS("tomove white\n");
15099     }
15100 }
15101
15102 void
15103 SetBlackToPlayEvent ()
15104 {
15105     if (gameMode == EditPosition) {
15106         blackPlaysFirst = TRUE;
15107         currentMove = 1;        /* kludge */
15108         DisplayBothClocks();
15109         currentMove = 0;
15110     } else if (gameMode == IcsExamining) {
15111         SendToICS(ics_prefix);
15112         SendToICS("tomove black\n");
15113     }
15114 }
15115
15116 void
15117 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15118 {
15119     char buf[MSG_SIZ];
15120     ChessSquare piece = boards[0][y][x];
15121     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15122     static int lastVariant;
15123
15124     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15125
15126     switch (selection) {
15127       case ClearBoard:
15128         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15129         MarkTargetSquares(1);
15130         CopyBoard(currentBoard, boards[0]);
15131         CopyBoard(menuBoard, initialPosition);
15132         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15133             SendToICS(ics_prefix);
15134             SendToICS("bsetup clear\n");
15135         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15136             SendToICS(ics_prefix);
15137             SendToICS("clearboard\n");
15138         } else {
15139             int nonEmpty = 0;
15140             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15141                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15142                 for (y = 0; y < BOARD_HEIGHT; y++) {
15143                     if (gameMode == IcsExamining) {
15144                         if (boards[currentMove][y][x] != EmptySquare) {
15145                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15146                                     AAA + x, ONE + y);
15147                             SendToICS(buf);
15148                         }
15149                     } else if(boards[0][y][x] != DarkSquare) {
15150                         if(boards[0][y][x] != p) nonEmpty++;
15151                         boards[0][y][x] = p;
15152                     }
15153                 }
15154             }
15155             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15156                 int r;
15157                 for(r = 0; r < BOARD_HEIGHT; r++) {
15158                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15159                     ChessSquare p = menuBoard[r][x];
15160                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15161                   }
15162                 }
15163                 DisplayMessage("Clicking clock again restores position", "");
15164                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15165                 if(!nonEmpty) { // asked to clear an empty board
15166                     CopyBoard(boards[0], menuBoard);
15167                 } else
15168                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15169                     CopyBoard(boards[0], initialPosition);
15170                 } else
15171                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15172                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15173                     CopyBoard(boards[0], erasedBoard);
15174                 } else
15175                     CopyBoard(erasedBoard, currentBoard);
15176
15177             }
15178         }
15179         if (gameMode == EditPosition) {
15180             DrawPosition(FALSE, boards[0]);
15181         }
15182         break;
15183
15184       case WhitePlay:
15185         SetWhiteToPlayEvent();
15186         break;
15187
15188       case BlackPlay:
15189         SetBlackToPlayEvent();
15190         break;
15191
15192       case EmptySquare:
15193         if (gameMode == IcsExamining) {
15194             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15195             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15196             SendToICS(buf);
15197         } else {
15198             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15199                 if(x == BOARD_LEFT-2) {
15200                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15201                     boards[0][y][1] = 0;
15202                 } else
15203                 if(x == BOARD_RGHT+1) {
15204                     if(y >= gameInfo.holdingsSize) break;
15205                     boards[0][y][BOARD_WIDTH-2] = 0;
15206                 } else break;
15207             }
15208             boards[0][y][x] = EmptySquare;
15209             DrawPosition(FALSE, boards[0]);
15210         }
15211         break;
15212
15213       case PromotePiece:
15214         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15215            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15216             selection = (ChessSquare) (PROMOTED piece);
15217         } else if(piece == EmptySquare) selection = WhiteSilver;
15218         else selection = (ChessSquare)((int)piece - 1);
15219         goto defaultlabel;
15220
15221       case DemotePiece:
15222         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15223            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15224             selection = (ChessSquare) (DEMOTED piece);
15225         } else if(piece == EmptySquare) selection = BlackSilver;
15226         else selection = (ChessSquare)((int)piece + 1);
15227         goto defaultlabel;
15228
15229       case WhiteQueen:
15230       case BlackQueen:
15231         if(gameInfo.variant == VariantShatranj ||
15232            gameInfo.variant == VariantXiangqi  ||
15233            gameInfo.variant == VariantCourier  ||
15234            gameInfo.variant == VariantASEAN    ||
15235            gameInfo.variant == VariantMakruk     )
15236             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15237         goto defaultlabel;
15238
15239       case WhiteKing:
15240       case BlackKing:
15241         if(gameInfo.variant == VariantXiangqi)
15242             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15243         if(gameInfo.variant == VariantKnightmate)
15244             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15245       default:
15246         defaultlabel:
15247         if (gameMode == IcsExamining) {
15248             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15249             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15250                      PieceToChar(selection), AAA + x, ONE + y);
15251             SendToICS(buf);
15252         } else {
15253             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15254                 int n;
15255                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15256                     n = PieceToNumber(selection - BlackPawn);
15257                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15258                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15259                     boards[0][BOARD_HEIGHT-1-n][1]++;
15260                 } else
15261                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15262                     n = PieceToNumber(selection);
15263                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15264                     boards[0][n][BOARD_WIDTH-1] = selection;
15265                     boards[0][n][BOARD_WIDTH-2]++;
15266                 }
15267             } else
15268             boards[0][y][x] = selection;
15269             DrawPosition(TRUE, boards[0]);
15270             ClearHighlights();
15271             fromX = fromY = -1;
15272         }
15273         break;
15274     }
15275 }
15276
15277
15278 void
15279 DropMenuEvent (ChessSquare selection, int x, int y)
15280 {
15281     ChessMove moveType;
15282
15283     switch (gameMode) {
15284       case IcsPlayingWhite:
15285       case MachinePlaysBlack:
15286         if (!WhiteOnMove(currentMove)) {
15287             DisplayMoveError(_("It is Black's turn"));
15288             return;
15289         }
15290         moveType = WhiteDrop;
15291         break;
15292       case IcsPlayingBlack:
15293       case MachinePlaysWhite:
15294         if (WhiteOnMove(currentMove)) {
15295             DisplayMoveError(_("It is White's turn"));
15296             return;
15297         }
15298         moveType = BlackDrop;
15299         break;
15300       case EditGame:
15301         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15302         break;
15303       default:
15304         return;
15305     }
15306
15307     if (moveType == BlackDrop && selection < BlackPawn) {
15308       selection = (ChessSquare) ((int) selection
15309                                  + (int) BlackPawn - (int) WhitePawn);
15310     }
15311     if (boards[currentMove][y][x] != EmptySquare) {
15312         DisplayMoveError(_("That square is occupied"));
15313         return;
15314     }
15315
15316     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15317 }
15318
15319 void
15320 AcceptEvent ()
15321 {
15322     /* Accept a pending offer of any kind from opponent */
15323
15324     if (appData.icsActive) {
15325         SendToICS(ics_prefix);
15326         SendToICS("accept\n");
15327     } else if (cmailMsgLoaded) {
15328         if (currentMove == cmailOldMove &&
15329             commentList[cmailOldMove] != NULL &&
15330             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15331                    "Black offers a draw" : "White offers a draw")) {
15332             TruncateGame();
15333             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15334             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15335         } else {
15336             DisplayError(_("There is no pending offer on this move"), 0);
15337             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15338         }
15339     } else {
15340         /* Not used for offers from chess program */
15341     }
15342 }
15343
15344 void
15345 DeclineEvent ()
15346 {
15347     /* Decline a pending offer of any kind from opponent */
15348
15349     if (appData.icsActive) {
15350         SendToICS(ics_prefix);
15351         SendToICS("decline\n");
15352     } else if (cmailMsgLoaded) {
15353         if (currentMove == cmailOldMove &&
15354             commentList[cmailOldMove] != NULL &&
15355             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15356                    "Black offers a draw" : "White offers a draw")) {
15357 #ifdef NOTDEF
15358             AppendComment(cmailOldMove, "Draw declined", TRUE);
15359             DisplayComment(cmailOldMove - 1, "Draw declined");
15360 #endif /*NOTDEF*/
15361         } else {
15362             DisplayError(_("There is no pending offer on this move"), 0);
15363         }
15364     } else {
15365         /* Not used for offers from chess program */
15366     }
15367 }
15368
15369 void
15370 RematchEvent ()
15371 {
15372     /* Issue ICS rematch command */
15373     if (appData.icsActive) {
15374         SendToICS(ics_prefix);
15375         SendToICS("rematch\n");
15376     }
15377 }
15378
15379 void
15380 CallFlagEvent ()
15381 {
15382     /* Call your opponent's flag (claim a win on time) */
15383     if (appData.icsActive) {
15384         SendToICS(ics_prefix);
15385         SendToICS("flag\n");
15386     } else {
15387         switch (gameMode) {
15388           default:
15389             return;
15390           case MachinePlaysWhite:
15391             if (whiteFlag) {
15392                 if (blackFlag)
15393                   GameEnds(GameIsDrawn, "Both players ran out of time",
15394                            GE_PLAYER);
15395                 else
15396                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15397             } else {
15398                 DisplayError(_("Your opponent is not out of time"), 0);
15399             }
15400             break;
15401           case MachinePlaysBlack:
15402             if (blackFlag) {
15403                 if (whiteFlag)
15404                   GameEnds(GameIsDrawn, "Both players ran out of time",
15405                            GE_PLAYER);
15406                 else
15407                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15408             } else {
15409                 DisplayError(_("Your opponent is not out of time"), 0);
15410             }
15411             break;
15412         }
15413     }
15414 }
15415
15416 void
15417 ClockClick (int which)
15418 {       // [HGM] code moved to back-end from winboard.c
15419         if(which) { // black clock
15420           if (gameMode == EditPosition || gameMode == IcsExamining) {
15421             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15422             SetBlackToPlayEvent();
15423           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15424                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15425           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15426           } else if (shiftKey) {
15427             AdjustClock(which, -1);
15428           } else if (gameMode == IcsPlayingWhite ||
15429                      gameMode == MachinePlaysBlack) {
15430             CallFlagEvent();
15431           }
15432         } else { // white clock
15433           if (gameMode == EditPosition || gameMode == IcsExamining) {
15434             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15435             SetWhiteToPlayEvent();
15436           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15437                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15438           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15439           } else if (shiftKey) {
15440             AdjustClock(which, -1);
15441           } else if (gameMode == IcsPlayingBlack ||
15442                    gameMode == MachinePlaysWhite) {
15443             CallFlagEvent();
15444           }
15445         }
15446 }
15447
15448 void
15449 DrawEvent ()
15450 {
15451     /* Offer draw or accept pending draw offer from opponent */
15452
15453     if (appData.icsActive) {
15454         /* Note: tournament rules require draw offers to be
15455            made after you make your move but before you punch
15456            your clock.  Currently ICS doesn't let you do that;
15457            instead, you immediately punch your clock after making
15458            a move, but you can offer a draw at any time. */
15459
15460         SendToICS(ics_prefix);
15461         SendToICS("draw\n");
15462         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15463     } else if (cmailMsgLoaded) {
15464         if (currentMove == cmailOldMove &&
15465             commentList[cmailOldMove] != NULL &&
15466             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15467                    "Black offers a draw" : "White offers a draw")) {
15468             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15469             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15470         } else if (currentMove == cmailOldMove + 1) {
15471             char *offer = WhiteOnMove(cmailOldMove) ?
15472               "White offers a draw" : "Black offers a draw";
15473             AppendComment(currentMove, offer, TRUE);
15474             DisplayComment(currentMove - 1, offer);
15475             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15476         } else {
15477             DisplayError(_("You must make your move before offering a draw"), 0);
15478             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15479         }
15480     } else if (first.offeredDraw) {
15481         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15482     } else {
15483         if (first.sendDrawOffers) {
15484             SendToProgram("draw\n", &first);
15485             userOfferedDraw = TRUE;
15486         }
15487     }
15488 }
15489
15490 void
15491 AdjournEvent ()
15492 {
15493     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15494
15495     if (appData.icsActive) {
15496         SendToICS(ics_prefix);
15497         SendToICS("adjourn\n");
15498     } else {
15499         /* Currently GNU Chess doesn't offer or accept Adjourns */
15500     }
15501 }
15502
15503
15504 void
15505 AbortEvent ()
15506 {
15507     /* Offer Abort or accept pending Abort offer from opponent */
15508
15509     if (appData.icsActive) {
15510         SendToICS(ics_prefix);
15511         SendToICS("abort\n");
15512     } else {
15513         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15514     }
15515 }
15516
15517 void
15518 ResignEvent ()
15519 {
15520     /* Resign.  You can do this even if it's not your turn. */
15521
15522     if (appData.icsActive) {
15523         SendToICS(ics_prefix);
15524         SendToICS("resign\n");
15525     } else {
15526         switch (gameMode) {
15527           case MachinePlaysWhite:
15528             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15529             break;
15530           case MachinePlaysBlack:
15531             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15532             break;
15533           case EditGame:
15534             if (cmailMsgLoaded) {
15535                 TruncateGame();
15536                 if (WhiteOnMove(cmailOldMove)) {
15537                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15538                 } else {
15539                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15540                 }
15541                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15542             }
15543             break;
15544           default:
15545             break;
15546         }
15547     }
15548 }
15549
15550
15551 void
15552 StopObservingEvent ()
15553 {
15554     /* Stop observing current games */
15555     SendToICS(ics_prefix);
15556     SendToICS("unobserve\n");
15557 }
15558
15559 void
15560 StopExaminingEvent ()
15561 {
15562     /* Stop observing current game */
15563     SendToICS(ics_prefix);
15564     SendToICS("unexamine\n");
15565 }
15566
15567 void
15568 ForwardInner (int target)
15569 {
15570     int limit; int oldSeekGraphUp = seekGraphUp;
15571
15572     if (appData.debugMode)
15573         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15574                 target, currentMove, forwardMostMove);
15575
15576     if (gameMode == EditPosition)
15577       return;
15578
15579     seekGraphUp = FALSE;
15580     MarkTargetSquares(1);
15581     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15582
15583     if (gameMode == PlayFromGameFile && !pausing)
15584       PauseEvent();
15585
15586     if (gameMode == IcsExamining && pausing)
15587       limit = pauseExamForwardMostMove;
15588     else
15589       limit = forwardMostMove;
15590
15591     if (target > limit) target = limit;
15592
15593     if (target > 0 && moveList[target - 1][0]) {
15594         int fromX, fromY, toX, toY;
15595         toX = moveList[target - 1][2] - AAA;
15596         toY = moveList[target - 1][3] - ONE;
15597         if (moveList[target - 1][1] == '@') {
15598             if (appData.highlightLastMove) {
15599                 SetHighlights(-1, -1, toX, toY);
15600             }
15601         } else {
15602             int viaX = moveList[target - 1][5] - AAA;
15603             int viaY = moveList[target - 1][6] - ONE;
15604             fromX = moveList[target - 1][0] - AAA;
15605             fromY = moveList[target - 1][1] - ONE;
15606             if (target == currentMove + 1) {
15607                 if(moveList[target - 1][4] == ';') { // multi-leg
15608                     ChessSquare piece = boards[currentMove][viaY][viaX];
15609                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15610                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15611                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15612                     boards[currentMove][viaY][viaX] = piece;
15613                 } else
15614                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15615             }
15616             if (appData.highlightLastMove) {
15617                 SetHighlights(fromX, fromY, toX, toY);
15618             }
15619         }
15620     }
15621     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15622         gameMode == Training || gameMode == PlayFromGameFile ||
15623         gameMode == AnalyzeFile) {
15624         while (currentMove < target) {
15625             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15626             SendMoveToProgram(currentMove++, &first);
15627         }
15628     } else {
15629         currentMove = target;
15630     }
15631
15632     if (gameMode == EditGame || gameMode == EndOfGame) {
15633         whiteTimeRemaining = timeRemaining[0][currentMove];
15634         blackTimeRemaining = timeRemaining[1][currentMove];
15635     }
15636     DisplayBothClocks();
15637     DisplayMove(currentMove - 1);
15638     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15639     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15640     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15641         DisplayComment(currentMove - 1, commentList[currentMove]);
15642     }
15643     ClearMap(); // [HGM] exclude: invalidate map
15644 }
15645
15646
15647 void
15648 ForwardEvent ()
15649 {
15650     if (gameMode == IcsExamining && !pausing) {
15651         SendToICS(ics_prefix);
15652         SendToICS("forward\n");
15653     } else {
15654         ForwardInner(currentMove + 1);
15655     }
15656 }
15657
15658 void
15659 ToEndEvent ()
15660 {
15661     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15662         /* to optimze, we temporarily turn off analysis mode while we feed
15663          * the remaining moves to the engine. Otherwise we get analysis output
15664          * after each move.
15665          */
15666         if (first.analysisSupport) {
15667           SendToProgram("exit\nforce\n", &first);
15668           first.analyzing = FALSE;
15669         }
15670     }
15671
15672     if (gameMode == IcsExamining && !pausing) {
15673         SendToICS(ics_prefix);
15674         SendToICS("forward 999999\n");
15675     } else {
15676         ForwardInner(forwardMostMove);
15677     }
15678
15679     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15680         /* we have fed all the moves, so reactivate analysis mode */
15681         SendToProgram("analyze\n", &first);
15682         first.analyzing = TRUE;
15683         /*first.maybeThinking = TRUE;*/
15684         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15685     }
15686 }
15687
15688 void
15689 BackwardInner (int target)
15690 {
15691     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15692
15693     if (appData.debugMode)
15694         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15695                 target, currentMove, forwardMostMove);
15696
15697     if (gameMode == EditPosition) return;
15698     seekGraphUp = FALSE;
15699     MarkTargetSquares(1);
15700     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15701     if (currentMove <= backwardMostMove) {
15702         ClearHighlights();
15703         DrawPosition(full_redraw, boards[currentMove]);
15704         return;
15705     }
15706     if (gameMode == PlayFromGameFile && !pausing)
15707       PauseEvent();
15708
15709     if (moveList[target][0]) {
15710         int fromX, fromY, toX, toY;
15711         toX = moveList[target][2] - AAA;
15712         toY = moveList[target][3] - ONE;
15713         if (moveList[target][1] == '@') {
15714             if (appData.highlightLastMove) {
15715                 SetHighlights(-1, -1, toX, toY);
15716             }
15717         } else {
15718             fromX = moveList[target][0] - AAA;
15719             fromY = moveList[target][1] - ONE;
15720             if (target == currentMove - 1) {
15721                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15722             }
15723             if (appData.highlightLastMove) {
15724                 SetHighlights(fromX, fromY, toX, toY);
15725             }
15726         }
15727     }
15728     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15729         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15730         while (currentMove > target) {
15731             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15732                 // null move cannot be undone. Reload program with move history before it.
15733                 int i;
15734                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15735                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15736                 }
15737                 SendBoard(&first, i);
15738               if(second.analyzing) SendBoard(&second, i);
15739                 for(currentMove=i; currentMove<target; currentMove++) {
15740                     SendMoveToProgram(currentMove, &first);
15741                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15742                 }
15743                 break;
15744             }
15745             SendToBoth("undo\n");
15746             currentMove--;
15747         }
15748     } else {
15749         currentMove = target;
15750     }
15751
15752     if (gameMode == EditGame || gameMode == EndOfGame) {
15753         whiteTimeRemaining = timeRemaining[0][currentMove];
15754         blackTimeRemaining = timeRemaining[1][currentMove];
15755     }
15756     DisplayBothClocks();
15757     DisplayMove(currentMove - 1);
15758     DrawPosition(full_redraw, boards[currentMove]);
15759     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15760     // [HGM] PV info: routine tests if comment empty
15761     DisplayComment(currentMove - 1, commentList[currentMove]);
15762     ClearMap(); // [HGM] exclude: invalidate map
15763 }
15764
15765 void
15766 BackwardEvent ()
15767 {
15768     if (gameMode == IcsExamining && !pausing) {
15769         SendToICS(ics_prefix);
15770         SendToICS("backward\n");
15771     } else {
15772         BackwardInner(currentMove - 1);
15773     }
15774 }
15775
15776 void
15777 ToStartEvent ()
15778 {
15779     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15780         /* to optimize, we temporarily turn off analysis mode while we undo
15781          * all the moves. Otherwise we get analysis output after each undo.
15782          */
15783         if (first.analysisSupport) {
15784           SendToProgram("exit\nforce\n", &first);
15785           first.analyzing = FALSE;
15786         }
15787     }
15788
15789     if (gameMode == IcsExamining && !pausing) {
15790         SendToICS(ics_prefix);
15791         SendToICS("backward 999999\n");
15792     } else {
15793         BackwardInner(backwardMostMove);
15794     }
15795
15796     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15797         /* we have fed all the moves, so reactivate analysis mode */
15798         SendToProgram("analyze\n", &first);
15799         first.analyzing = TRUE;
15800         /*first.maybeThinking = TRUE;*/
15801         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15802     }
15803 }
15804
15805 void
15806 ToNrEvent (int to)
15807 {
15808   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15809   if (to >= forwardMostMove) to = forwardMostMove;
15810   if (to <= backwardMostMove) to = backwardMostMove;
15811   if (to < currentMove) {
15812     BackwardInner(to);
15813   } else {
15814     ForwardInner(to);
15815   }
15816 }
15817
15818 void
15819 RevertEvent (Boolean annotate)
15820 {
15821     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15822         return;
15823     }
15824     if (gameMode != IcsExamining) {
15825         DisplayError(_("You are not examining a game"), 0);
15826         return;
15827     }
15828     if (pausing) {
15829         DisplayError(_("You can't revert while pausing"), 0);
15830         return;
15831     }
15832     SendToICS(ics_prefix);
15833     SendToICS("revert\n");
15834 }
15835
15836 void
15837 RetractMoveEvent ()
15838 {
15839     switch (gameMode) {
15840       case MachinePlaysWhite:
15841       case MachinePlaysBlack:
15842         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15843             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15844             return;
15845         }
15846         if (forwardMostMove < 2) return;
15847         currentMove = forwardMostMove = forwardMostMove - 2;
15848         whiteTimeRemaining = timeRemaining[0][currentMove];
15849         blackTimeRemaining = timeRemaining[1][currentMove];
15850         DisplayBothClocks();
15851         DisplayMove(currentMove - 1);
15852         ClearHighlights();/*!! could figure this out*/
15853         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15854         SendToProgram("remove\n", &first);
15855         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15856         break;
15857
15858       case BeginningOfGame:
15859       default:
15860         break;
15861
15862       case IcsPlayingWhite:
15863       case IcsPlayingBlack:
15864         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15865             SendToICS(ics_prefix);
15866             SendToICS("takeback 2\n");
15867         } else {
15868             SendToICS(ics_prefix);
15869             SendToICS("takeback 1\n");
15870         }
15871         break;
15872     }
15873 }
15874
15875 void
15876 MoveNowEvent ()
15877 {
15878     ChessProgramState *cps;
15879
15880     switch (gameMode) {
15881       case MachinePlaysWhite:
15882         if (!WhiteOnMove(forwardMostMove)) {
15883             DisplayError(_("It is your turn"), 0);
15884             return;
15885         }
15886         cps = &first;
15887         break;
15888       case MachinePlaysBlack:
15889         if (WhiteOnMove(forwardMostMove)) {
15890             DisplayError(_("It is your turn"), 0);
15891             return;
15892         }
15893         cps = &first;
15894         break;
15895       case TwoMachinesPlay:
15896         if (WhiteOnMove(forwardMostMove) ==
15897             (first.twoMachinesColor[0] == 'w')) {
15898             cps = &first;
15899         } else {
15900             cps = &second;
15901         }
15902         break;
15903       case BeginningOfGame:
15904       default:
15905         return;
15906     }
15907     SendToProgram("?\n", cps);
15908 }
15909
15910 void
15911 TruncateGameEvent ()
15912 {
15913     EditGameEvent();
15914     if (gameMode != EditGame) return;
15915     TruncateGame();
15916 }
15917
15918 void
15919 TruncateGame ()
15920 {
15921     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15922     if (forwardMostMove > currentMove) {
15923         if (gameInfo.resultDetails != NULL) {
15924             free(gameInfo.resultDetails);
15925             gameInfo.resultDetails = NULL;
15926             gameInfo.result = GameUnfinished;
15927         }
15928         forwardMostMove = currentMove;
15929         HistorySet(parseList, backwardMostMove, forwardMostMove,
15930                    currentMove-1);
15931     }
15932 }
15933
15934 void
15935 HintEvent ()
15936 {
15937     if (appData.noChessProgram) return;
15938     switch (gameMode) {
15939       case MachinePlaysWhite:
15940         if (WhiteOnMove(forwardMostMove)) {
15941             DisplayError(_("Wait until your turn."), 0);
15942             return;
15943         }
15944         break;
15945       case BeginningOfGame:
15946       case MachinePlaysBlack:
15947         if (!WhiteOnMove(forwardMostMove)) {
15948             DisplayError(_("Wait until your turn."), 0);
15949             return;
15950         }
15951         break;
15952       default:
15953         DisplayError(_("No hint available"), 0);
15954         return;
15955     }
15956     SendToProgram("hint\n", &first);
15957     hintRequested = TRUE;
15958 }
15959
15960 int
15961 SaveSelected (FILE *g, int dummy, char *dummy2)
15962 {
15963     ListGame * lg = (ListGame *) gameList.head;
15964     int nItem, cnt=0;
15965     FILE *f;
15966
15967     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15968         DisplayError(_("Game list not loaded or empty"), 0);
15969         return 0;
15970     }
15971
15972     creatingBook = TRUE; // suppresses stuff during load game
15973
15974     /* Get list size */
15975     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15976         if(lg->position >= 0) { // selected?
15977             LoadGame(f, nItem, "", TRUE);
15978             SaveGamePGN2(g); // leaves g open
15979             cnt++; DoEvents();
15980         }
15981         lg = (ListGame *) lg->node.succ;
15982     }
15983
15984     fclose(g);
15985     creatingBook = FALSE;
15986
15987     return cnt;
15988 }
15989
15990 void
15991 CreateBookEvent ()
15992 {
15993     ListGame * lg = (ListGame *) gameList.head;
15994     FILE *f, *g;
15995     int nItem;
15996     static int secondTime = FALSE;
15997
15998     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15999         DisplayError(_("Game list not loaded or empty"), 0);
16000         return;
16001     }
16002
16003     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16004         fclose(g);
16005         secondTime++;
16006         DisplayNote(_("Book file exists! Try again for overwrite."));
16007         return;
16008     }
16009
16010     creatingBook = TRUE;
16011     secondTime = FALSE;
16012
16013     /* Get list size */
16014     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16015         if(lg->position >= 0) {
16016             LoadGame(f, nItem, "", TRUE);
16017             AddGameToBook(TRUE);
16018             DoEvents();
16019         }
16020         lg = (ListGame *) lg->node.succ;
16021     }
16022
16023     creatingBook = FALSE;
16024     FlushBook();
16025 }
16026
16027 void
16028 BookEvent ()
16029 {
16030     if (appData.noChessProgram) return;
16031     switch (gameMode) {
16032       case MachinePlaysWhite:
16033         if (WhiteOnMove(forwardMostMove)) {
16034             DisplayError(_("Wait until your turn."), 0);
16035             return;
16036         }
16037         break;
16038       case BeginningOfGame:
16039       case MachinePlaysBlack:
16040         if (!WhiteOnMove(forwardMostMove)) {
16041             DisplayError(_("Wait until your turn."), 0);
16042             return;
16043         }
16044         break;
16045       case EditPosition:
16046         EditPositionDone(TRUE);
16047         break;
16048       case TwoMachinesPlay:
16049         return;
16050       default:
16051         break;
16052     }
16053     SendToProgram("bk\n", &first);
16054     bookOutput[0] = NULLCHAR;
16055     bookRequested = TRUE;
16056 }
16057
16058 void
16059 AboutGameEvent ()
16060 {
16061     char *tags = PGNTags(&gameInfo);
16062     TagsPopUp(tags, CmailMsg());
16063     free(tags);
16064 }
16065
16066 /* end button procedures */
16067
16068 void
16069 PrintPosition (FILE *fp, int move)
16070 {
16071     int i, j;
16072
16073     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16074         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16075             char c = PieceToChar(boards[move][i][j]);
16076             fputc(c == 'x' ? '.' : c, fp);
16077             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16078         }
16079     }
16080     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16081       fprintf(fp, "white to play\n");
16082     else
16083       fprintf(fp, "black to play\n");
16084 }
16085
16086 void
16087 PrintOpponents (FILE *fp)
16088 {
16089     if (gameInfo.white != NULL) {
16090         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16091     } else {
16092         fprintf(fp, "\n");
16093     }
16094 }
16095
16096 /* Find last component of program's own name, using some heuristics */
16097 void
16098 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16099 {
16100     char *p, *q, c;
16101     int local = (strcmp(host, "localhost") == 0);
16102     while (!local && (p = strchr(prog, ';')) != NULL) {
16103         p++;
16104         while (*p == ' ') p++;
16105         prog = p;
16106     }
16107     if (*prog == '"' || *prog == '\'') {
16108         q = strchr(prog + 1, *prog);
16109     } else {
16110         q = strchr(prog, ' ');
16111     }
16112     if (q == NULL) q = prog + strlen(prog);
16113     p = q;
16114     while (p >= prog && *p != '/' && *p != '\\') p--;
16115     p++;
16116     if(p == prog && *p == '"') p++;
16117     c = *q; *q = 0;
16118     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16119     memcpy(buf, p, q - p);
16120     buf[q - p] = NULLCHAR;
16121     if (!local) {
16122         strcat(buf, "@");
16123         strcat(buf, host);
16124     }
16125 }
16126
16127 char *
16128 TimeControlTagValue ()
16129 {
16130     char buf[MSG_SIZ];
16131     if (!appData.clockMode) {
16132       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16133     } else if (movesPerSession > 0) {
16134       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16135     } else if (timeIncrement == 0) {
16136       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16137     } else {
16138       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16139     }
16140     return StrSave(buf);
16141 }
16142
16143 void
16144 SetGameInfo ()
16145 {
16146     /* This routine is used only for certain modes */
16147     VariantClass v = gameInfo.variant;
16148     ChessMove r = GameUnfinished;
16149     char *p = NULL;
16150
16151     if(keepInfo) return;
16152
16153     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16154         r = gameInfo.result;
16155         p = gameInfo.resultDetails;
16156         gameInfo.resultDetails = NULL;
16157     }
16158     ClearGameInfo(&gameInfo);
16159     gameInfo.variant = v;
16160
16161     switch (gameMode) {
16162       case MachinePlaysWhite:
16163         gameInfo.event = StrSave( appData.pgnEventHeader );
16164         gameInfo.site = StrSave(HostName());
16165         gameInfo.date = PGNDate();
16166         gameInfo.round = StrSave("-");
16167         gameInfo.white = StrSave(first.tidy);
16168         gameInfo.black = StrSave(UserName());
16169         gameInfo.timeControl = TimeControlTagValue();
16170         break;
16171
16172       case MachinePlaysBlack:
16173         gameInfo.event = StrSave( appData.pgnEventHeader );
16174         gameInfo.site = StrSave(HostName());
16175         gameInfo.date = PGNDate();
16176         gameInfo.round = StrSave("-");
16177         gameInfo.white = StrSave(UserName());
16178         gameInfo.black = StrSave(first.tidy);
16179         gameInfo.timeControl = TimeControlTagValue();
16180         break;
16181
16182       case TwoMachinesPlay:
16183         gameInfo.event = StrSave( appData.pgnEventHeader );
16184         gameInfo.site = StrSave(HostName());
16185         gameInfo.date = PGNDate();
16186         if (roundNr > 0) {
16187             char buf[MSG_SIZ];
16188             snprintf(buf, MSG_SIZ, "%d", roundNr);
16189             gameInfo.round = StrSave(buf);
16190         } else {
16191             gameInfo.round = StrSave("-");
16192         }
16193         if (first.twoMachinesColor[0] == 'w') {
16194             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16195             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16196         } else {
16197             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16198             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16199         }
16200         gameInfo.timeControl = TimeControlTagValue();
16201         break;
16202
16203       case EditGame:
16204         gameInfo.event = StrSave("Edited game");
16205         gameInfo.site = StrSave(HostName());
16206         gameInfo.date = PGNDate();
16207         gameInfo.round = StrSave("-");
16208         gameInfo.white = StrSave("-");
16209         gameInfo.black = StrSave("-");
16210         gameInfo.result = r;
16211         gameInfo.resultDetails = p;
16212         break;
16213
16214       case EditPosition:
16215         gameInfo.event = StrSave("Edited position");
16216         gameInfo.site = StrSave(HostName());
16217         gameInfo.date = PGNDate();
16218         gameInfo.round = StrSave("-");
16219         gameInfo.white = StrSave("-");
16220         gameInfo.black = StrSave("-");
16221         break;
16222
16223       case IcsPlayingWhite:
16224       case IcsPlayingBlack:
16225       case IcsObserving:
16226       case IcsExamining:
16227         break;
16228
16229       case PlayFromGameFile:
16230         gameInfo.event = StrSave("Game from non-PGN file");
16231         gameInfo.site = StrSave(HostName());
16232         gameInfo.date = PGNDate();
16233         gameInfo.round = StrSave("-");
16234         gameInfo.white = StrSave("?");
16235         gameInfo.black = StrSave("?");
16236         break;
16237
16238       default:
16239         break;
16240     }
16241 }
16242
16243 void
16244 ReplaceComment (int index, char *text)
16245 {
16246     int len;
16247     char *p;
16248     float score;
16249
16250     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16251        pvInfoList[index-1].depth == len &&
16252        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16253        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16254     while (*text == '\n') text++;
16255     len = strlen(text);
16256     while (len > 0 && text[len - 1] == '\n') len--;
16257
16258     if (commentList[index] != NULL)
16259       free(commentList[index]);
16260
16261     if (len == 0) {
16262         commentList[index] = NULL;
16263         return;
16264     }
16265   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16266       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16267       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16268     commentList[index] = (char *) malloc(len + 2);
16269     strncpy(commentList[index], text, len);
16270     commentList[index][len] = '\n';
16271     commentList[index][len + 1] = NULLCHAR;
16272   } else {
16273     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16274     char *p;
16275     commentList[index] = (char *) malloc(len + 7);
16276     safeStrCpy(commentList[index], "{\n", 3);
16277     safeStrCpy(commentList[index]+2, text, len+1);
16278     commentList[index][len+2] = NULLCHAR;
16279     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16280     strcat(commentList[index], "\n}\n");
16281   }
16282 }
16283
16284 void
16285 CrushCRs (char *text)
16286 {
16287   char *p = text;
16288   char *q = text;
16289   char ch;
16290
16291   do {
16292     ch = *p++;
16293     if (ch == '\r') continue;
16294     *q++ = ch;
16295   } while (ch != '\0');
16296 }
16297
16298 void
16299 AppendComment (int index, char *text, Boolean addBraces)
16300 /* addBraces  tells if we should add {} */
16301 {
16302     int oldlen, len;
16303     char *old;
16304
16305 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16306     if(addBraces == 3) addBraces = 0; else // force appending literally
16307     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16308
16309     CrushCRs(text);
16310     while (*text == '\n') text++;
16311     len = strlen(text);
16312     while (len > 0 && text[len - 1] == '\n') len--;
16313     text[len] = NULLCHAR;
16314
16315     if (len == 0) return;
16316
16317     if (commentList[index] != NULL) {
16318       Boolean addClosingBrace = addBraces;
16319         old = commentList[index];
16320         oldlen = strlen(old);
16321         while(commentList[index][oldlen-1] ==  '\n')
16322           commentList[index][--oldlen] = NULLCHAR;
16323         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16324         safeStrCpy(commentList[index], old, oldlen + len + 6);
16325         free(old);
16326         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16327         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16328           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16329           while (*text == '\n') { text++; len--; }
16330           commentList[index][--oldlen] = NULLCHAR;
16331       }
16332         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16333         else          strcat(commentList[index], "\n");
16334         strcat(commentList[index], text);
16335         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16336         else          strcat(commentList[index], "\n");
16337     } else {
16338         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16339         if(addBraces)
16340           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16341         else commentList[index][0] = NULLCHAR;
16342         strcat(commentList[index], text);
16343         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16344         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16345     }
16346 }
16347
16348 static char *
16349 FindStr (char * text, char * sub_text)
16350 {
16351     char * result = strstr( text, sub_text );
16352
16353     if( result != NULL ) {
16354         result += strlen( sub_text );
16355     }
16356
16357     return result;
16358 }
16359
16360 /* [AS] Try to extract PV info from PGN comment */
16361 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16362 char *
16363 GetInfoFromComment (int index, char * text)
16364 {
16365     char * sep = text, *p;
16366
16367     if( text != NULL && index > 0 ) {
16368         int score = 0;
16369         int depth = 0;
16370         int time = -1, sec = 0, deci;
16371         char * s_eval = FindStr( text, "[%eval " );
16372         char * s_emt = FindStr( text, "[%emt " );
16373 #if 0
16374         if( s_eval != NULL || s_emt != NULL ) {
16375 #else
16376         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16377 #endif
16378             /* New style */
16379             char delim;
16380
16381             if( s_eval != NULL ) {
16382                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16383                     return text;
16384                 }
16385
16386                 if( delim != ']' ) {
16387                     return text;
16388                 }
16389             }
16390
16391             if( s_emt != NULL ) {
16392             }
16393                 return text;
16394         }
16395         else {
16396             /* We expect something like: [+|-]nnn.nn/dd */
16397             int score_lo = 0;
16398
16399             if(*text != '{') return text; // [HGM] braces: must be normal comment
16400
16401             sep = strchr( text, '/' );
16402             if( sep == NULL || sep < (text+4) ) {
16403                 return text;
16404             }
16405
16406             p = text;
16407             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16408             if(p[1] == '(') { // comment starts with PV
16409                p = strchr(p, ')'); // locate end of PV
16410                if(p == NULL || sep < p+5) return text;
16411                // at this point we have something like "{(.*) +0.23/6 ..."
16412                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16413                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16414                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16415             }
16416             time = -1; sec = -1; deci = -1;
16417             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16418                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16419                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16420                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16421                 return text;
16422             }
16423
16424             if( score_lo < 0 || score_lo >= 100 ) {
16425                 return text;
16426             }
16427
16428             if(sec >= 0) time = 600*time + 10*sec; else
16429             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16430
16431             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16432
16433             /* [HGM] PV time: now locate end of PV info */
16434             while( *++sep >= '0' && *sep <= '9'); // strip depth
16435             if(time >= 0)
16436             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16437             if(sec >= 0)
16438             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16439             if(deci >= 0)
16440             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16441             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16442         }
16443
16444         if( depth <= 0 ) {
16445             return text;
16446         }
16447
16448         if( time < 0 ) {
16449             time = -1;
16450         }
16451
16452         pvInfoList[index-1].depth = depth;
16453         pvInfoList[index-1].score = score;
16454         pvInfoList[index-1].time  = 10*time; // centi-sec
16455         if(*sep == '}') *sep = 0; else *--sep = '{';
16456         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16457     }
16458     return sep;
16459 }
16460
16461 void
16462 SendToProgram (char *message, ChessProgramState *cps)
16463 {
16464     int count, outCount, error;
16465     char buf[MSG_SIZ];
16466
16467     if (cps->pr == NoProc) return;
16468     Attention(cps);
16469
16470     if (appData.debugMode) {
16471         TimeMark now;
16472         GetTimeMark(&now);
16473         fprintf(debugFP, "%ld >%-6s: %s",
16474                 SubtractTimeMarks(&now, &programStartTime),
16475                 cps->which, message);
16476         if(serverFP)
16477             fprintf(serverFP, "%ld >%-6s: %s",
16478                 SubtractTimeMarks(&now, &programStartTime),
16479                 cps->which, message), fflush(serverFP);
16480     }
16481
16482     count = strlen(message);
16483     outCount = OutputToProcess(cps->pr, message, count, &error);
16484     if (outCount < count && !exiting
16485                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16486       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16487       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16488         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16489             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16490                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16491                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16492                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16493             } else {
16494                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16495                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16496                 gameInfo.result = res;
16497             }
16498             gameInfo.resultDetails = StrSave(buf);
16499         }
16500         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16501         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16502     }
16503 }
16504
16505 void
16506 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16507 {
16508     char *end_str;
16509     char buf[MSG_SIZ];
16510     ChessProgramState *cps = (ChessProgramState *)closure;
16511
16512     if (isr != cps->isr) return; /* Killed intentionally */
16513     if (count <= 0) {
16514         if (count == 0) {
16515             RemoveInputSource(cps->isr);
16516             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16517                     _(cps->which), cps->program);
16518             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16519             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16520                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16521                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16522                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16523                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16524                 } else {
16525                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16526                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16527                     gameInfo.result = res;
16528                 }
16529                 gameInfo.resultDetails = StrSave(buf);
16530             }
16531             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16532             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16533         } else {
16534             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16535                     _(cps->which), cps->program);
16536             RemoveInputSource(cps->isr);
16537
16538             /* [AS] Program is misbehaving badly... kill it */
16539             if( count == -2 ) {
16540                 DestroyChildProcess( cps->pr, 9 );
16541                 cps->pr = NoProc;
16542             }
16543
16544             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16545         }
16546         return;
16547     }
16548
16549     if ((end_str = strchr(message, '\r')) != NULL)
16550       *end_str = NULLCHAR;
16551     if ((end_str = strchr(message, '\n')) != NULL)
16552       *end_str = NULLCHAR;
16553
16554     if (appData.debugMode) {
16555         TimeMark now; int print = 1;
16556         char *quote = ""; char c; int i;
16557
16558         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16559                 char start = message[0];
16560                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16561                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16562                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16563                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16564                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16565                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16566                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16567                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16568                    sscanf(message, "hint: %c", &c)!=1 &&
16569                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16570                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16571                     print = (appData.engineComments >= 2);
16572                 }
16573                 message[0] = start; // restore original message
16574         }
16575         if(print) {
16576                 GetTimeMark(&now);
16577                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16578                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16579                         quote,
16580                         message);
16581                 if(serverFP)
16582                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16583                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16584                         quote,
16585                         message), fflush(serverFP);
16586         }
16587     }
16588
16589     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16590     if (appData.icsEngineAnalyze) {
16591         if (strstr(message, "whisper") != NULL ||
16592              strstr(message, "kibitz") != NULL ||
16593             strstr(message, "tellics") != NULL) return;
16594     }
16595
16596     HandleMachineMove(message, cps);
16597 }
16598
16599
16600 void
16601 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16602 {
16603     char buf[MSG_SIZ];
16604     int seconds;
16605
16606     if( timeControl_2 > 0 ) {
16607         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16608             tc = timeControl_2;
16609         }
16610     }
16611     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16612     inc /= cps->timeOdds;
16613     st  /= cps->timeOdds;
16614
16615     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16616
16617     if (st > 0) {
16618       /* Set exact time per move, normally using st command */
16619       if (cps->stKludge) {
16620         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16621         seconds = st % 60;
16622         if (seconds == 0) {
16623           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16624         } else {
16625           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16626         }
16627       } else {
16628         snprintf(buf, MSG_SIZ, "st %d\n", st);
16629       }
16630     } else {
16631       /* Set conventional or incremental time control, using level command */
16632       if (seconds == 0) {
16633         /* Note old gnuchess bug -- minutes:seconds used to not work.
16634            Fixed in later versions, but still avoid :seconds
16635            when seconds is 0. */
16636         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16637       } else {
16638         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16639                  seconds, inc/1000.);
16640       }
16641     }
16642     SendToProgram(buf, cps);
16643
16644     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16645     /* Orthogonally, limit search to given depth */
16646     if (sd > 0) {
16647       if (cps->sdKludge) {
16648         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16649       } else {
16650         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16651       }
16652       SendToProgram(buf, cps);
16653     }
16654
16655     if(cps->nps >= 0) { /* [HGM] nps */
16656         if(cps->supportsNPS == FALSE)
16657           cps->nps = -1; // don't use if engine explicitly says not supported!
16658         else {
16659           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16660           SendToProgram(buf, cps);
16661         }
16662     }
16663 }
16664
16665 ChessProgramState *
16666 WhitePlayer ()
16667 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16668 {
16669     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16670        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16671         return &second;
16672     return &first;
16673 }
16674
16675 void
16676 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16677 {
16678     char message[MSG_SIZ];
16679     long time, otime;
16680
16681     /* Note: this routine must be called when the clocks are stopped
16682        or when they have *just* been set or switched; otherwise
16683        it will be off by the time since the current tick started.
16684     */
16685     if (machineWhite) {
16686         time = whiteTimeRemaining / 10;
16687         otime = blackTimeRemaining / 10;
16688     } else {
16689         time = blackTimeRemaining / 10;
16690         otime = whiteTimeRemaining / 10;
16691     }
16692     /* [HGM] translate opponent's time by time-odds factor */
16693     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16694
16695     if (time <= 0) time = 1;
16696     if (otime <= 0) otime = 1;
16697
16698     snprintf(message, MSG_SIZ, "time %ld\n", time);
16699     SendToProgram(message, cps);
16700
16701     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16702     SendToProgram(message, cps);
16703 }
16704
16705 char *
16706 EngineDefinedVariant (ChessProgramState *cps, int n)
16707 {   // return name of n-th unknown variant that engine supports
16708     static char buf[MSG_SIZ];
16709     char *p, *s = cps->variants;
16710     if(!s) return NULL;
16711     do { // parse string from variants feature
16712       VariantClass v;
16713         p = strchr(s, ',');
16714         if(p) *p = NULLCHAR;
16715       v = StringToVariant(s);
16716       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16717         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16718             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16719                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16720                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16721                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16722             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16723         }
16724         if(p) *p++ = ',';
16725         if(n < 0) return buf;
16726     } while(s = p);
16727     return NULL;
16728 }
16729
16730 int
16731 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16732 {
16733   char buf[MSG_SIZ];
16734   int len = strlen(name);
16735   int val;
16736
16737   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16738     (*p) += len + 1;
16739     sscanf(*p, "%d", &val);
16740     *loc = (val != 0);
16741     while (**p && **p != ' ')
16742       (*p)++;
16743     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16744     SendToProgram(buf, cps);
16745     return TRUE;
16746   }
16747   return FALSE;
16748 }
16749
16750 int
16751 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16752 {
16753   char buf[MSG_SIZ];
16754   int len = strlen(name);
16755   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16756     (*p) += len + 1;
16757     sscanf(*p, "%d", loc);
16758     while (**p && **p != ' ') (*p)++;
16759     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16760     SendToProgram(buf, cps);
16761     return TRUE;
16762   }
16763   return FALSE;
16764 }
16765
16766 int
16767 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16768 {
16769   char buf[MSG_SIZ];
16770   int len = strlen(name);
16771   if (strncmp((*p), name, len) == 0
16772       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16773     (*p) += len + 2;
16774     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16775     sscanf(*p, "%[^\"]", *loc);
16776     while (**p && **p != '\"') (*p)++;
16777     if (**p == '\"') (*p)++;
16778     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16779     SendToProgram(buf, cps);
16780     return TRUE;
16781   }
16782   return FALSE;
16783 }
16784
16785 int
16786 ParseOption (Option *opt, ChessProgramState *cps)
16787 // [HGM] options: process the string that defines an engine option, and determine
16788 // name, type, default value, and allowed value range
16789 {
16790         char *p, *q, buf[MSG_SIZ];
16791         int n, min = (-1)<<31, max = 1<<31, def;
16792
16793         if(p = strstr(opt->name, " -spin ")) {
16794             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16795             if(max < min) max = min; // enforce consistency
16796             if(def < min) def = min;
16797             if(def > max) def = max;
16798             opt->value = def;
16799             opt->min = min;
16800             opt->max = max;
16801             opt->type = Spin;
16802         } else if((p = strstr(opt->name, " -slider "))) {
16803             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16804             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16805             if(max < min) max = min; // enforce consistency
16806             if(def < min) def = min;
16807             if(def > max) def = max;
16808             opt->value = def;
16809             opt->min = min;
16810             opt->max = max;
16811             opt->type = Spin; // Slider;
16812         } else if((p = strstr(opt->name, " -string "))) {
16813             opt->textValue = p+9;
16814             opt->type = TextBox;
16815         } else if((p = strstr(opt->name, " -file "))) {
16816             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16817             opt->textValue = p+7;
16818             opt->type = FileName; // FileName;
16819         } else if((p = strstr(opt->name, " -path "))) {
16820             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16821             opt->textValue = p+7;
16822             opt->type = PathName; // PathName;
16823         } else if(p = strstr(opt->name, " -check ")) {
16824             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16825             opt->value = (def != 0);
16826             opt->type = CheckBox;
16827         } else if(p = strstr(opt->name, " -combo ")) {
16828             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16829             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16830             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16831             opt->value = n = 0;
16832             while(q = StrStr(q, " /// ")) {
16833                 n++; *q = 0;    // count choices, and null-terminate each of them
16834                 q += 5;
16835                 if(*q == '*') { // remember default, which is marked with * prefix
16836                     q++;
16837                     opt->value = n;
16838                 }
16839                 cps->comboList[cps->comboCnt++] = q;
16840             }
16841             cps->comboList[cps->comboCnt++] = NULL;
16842             opt->max = n + 1;
16843             opt->type = ComboBox;
16844         } else if(p = strstr(opt->name, " -button")) {
16845             opt->type = Button;
16846         } else if(p = strstr(opt->name, " -save")) {
16847             opt->type = SaveButton;
16848         } else return FALSE;
16849         *p = 0; // terminate option name
16850         // now look if the command-line options define a setting for this engine option.
16851         if(cps->optionSettings && cps->optionSettings[0])
16852             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16853         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16854           snprintf(buf, MSG_SIZ, "option %s", p);
16855                 if(p = strstr(buf, ",")) *p = 0;
16856                 if(q = strchr(buf, '=')) switch(opt->type) {
16857                     case ComboBox:
16858                         for(n=0; n<opt->max; n++)
16859                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16860                         break;
16861                     case TextBox:
16862                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16863                         break;
16864                     case Spin:
16865                     case CheckBox:
16866                         opt->value = atoi(q+1);
16867                     default:
16868                         break;
16869                 }
16870                 strcat(buf, "\n");
16871                 SendToProgram(buf, cps);
16872         }
16873         return TRUE;
16874 }
16875
16876 void
16877 FeatureDone (ChessProgramState *cps, int val)
16878 {
16879   DelayedEventCallback cb = GetDelayedEvent();
16880   if ((cb == InitBackEnd3 && cps == &first) ||
16881       (cb == SettingsMenuIfReady && cps == &second) ||
16882       (cb == LoadEngine) ||
16883       (cb == TwoMachinesEventIfReady)) {
16884     CancelDelayedEvent();
16885     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16886   }
16887   cps->initDone = val;
16888   if(val) cps->reload = FALSE;
16889 }
16890
16891 /* Parse feature command from engine */
16892 void
16893 ParseFeatures (char *args, ChessProgramState *cps)
16894 {
16895   char *p = args;
16896   char *q = NULL;
16897   int val;
16898   char buf[MSG_SIZ];
16899
16900   for (;;) {
16901     while (*p == ' ') p++;
16902     if (*p == NULLCHAR) return;
16903
16904     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16905     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16906     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16907     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16908     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16909     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16910     if (BoolFeature(&p, "reuse", &val, cps)) {
16911       /* Engine can disable reuse, but can't enable it if user said no */
16912       if (!val) cps->reuse = FALSE;
16913       continue;
16914     }
16915     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16916     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16917       if (gameMode == TwoMachinesPlay) {
16918         DisplayTwoMachinesTitle();
16919       } else {
16920         DisplayTitle("");
16921       }
16922       continue;
16923     }
16924     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16925     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16926     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16927     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16928     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16929     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16930     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16931     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16932     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16933     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16934     if (IntFeature(&p, "done", &val, cps)) {
16935       FeatureDone(cps, val);
16936       continue;
16937     }
16938     /* Added by Tord: */
16939     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16940     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16941     /* End of additions by Tord */
16942
16943     /* [HGM] added features: */
16944     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16945     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16946     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16947     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16948     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16949     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16950     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16951     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16952         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16953         FREE(cps->option[cps->nrOptions].name);
16954         cps->option[cps->nrOptions].name = q; q = NULL;
16955         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16956           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16957             SendToProgram(buf, cps);
16958             continue;
16959         }
16960         if(cps->nrOptions >= MAX_OPTIONS) {
16961             cps->nrOptions--;
16962             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16963             DisplayError(buf, 0);
16964         }
16965         continue;
16966     }
16967     /* End of additions by HGM */
16968
16969     /* unknown feature: complain and skip */
16970     q = p;
16971     while (*q && *q != '=') q++;
16972     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16973     SendToProgram(buf, cps);
16974     p = q;
16975     if (*p == '=') {
16976       p++;
16977       if (*p == '\"') {
16978         p++;
16979         while (*p && *p != '\"') p++;
16980         if (*p == '\"') p++;
16981       } else {
16982         while (*p && *p != ' ') p++;
16983       }
16984     }
16985   }
16986
16987 }
16988
16989 void
16990 PeriodicUpdatesEvent (int newState)
16991 {
16992     if (newState == appData.periodicUpdates)
16993       return;
16994
16995     appData.periodicUpdates=newState;
16996
16997     /* Display type changes, so update it now */
16998 //    DisplayAnalysis();
16999
17000     /* Get the ball rolling again... */
17001     if (newState) {
17002         AnalysisPeriodicEvent(1);
17003         StartAnalysisClock();
17004     }
17005 }
17006
17007 void
17008 PonderNextMoveEvent (int newState)
17009 {
17010     if (newState == appData.ponderNextMove) return;
17011     if (gameMode == EditPosition) EditPositionDone(TRUE);
17012     if (newState) {
17013         SendToProgram("hard\n", &first);
17014         if (gameMode == TwoMachinesPlay) {
17015             SendToProgram("hard\n", &second);
17016         }
17017     } else {
17018         SendToProgram("easy\n", &first);
17019         thinkOutput[0] = NULLCHAR;
17020         if (gameMode == TwoMachinesPlay) {
17021             SendToProgram("easy\n", &second);
17022         }
17023     }
17024     appData.ponderNextMove = newState;
17025 }
17026
17027 void
17028 NewSettingEvent (int option, int *feature, char *command, int value)
17029 {
17030     char buf[MSG_SIZ];
17031
17032     if (gameMode == EditPosition) EditPositionDone(TRUE);
17033     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17034     if(feature == NULL || *feature) SendToProgram(buf, &first);
17035     if (gameMode == TwoMachinesPlay) {
17036         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17037     }
17038 }
17039
17040 void
17041 ShowThinkingEvent ()
17042 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17043 {
17044     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17045     int newState = appData.showThinking
17046         // [HGM] thinking: other features now need thinking output as well
17047         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17048
17049     if (oldState == newState) return;
17050     oldState = newState;
17051     if (gameMode == EditPosition) EditPositionDone(TRUE);
17052     if (oldState) {
17053         SendToProgram("post\n", &first);
17054         if (gameMode == TwoMachinesPlay) {
17055             SendToProgram("post\n", &second);
17056         }
17057     } else {
17058         SendToProgram("nopost\n", &first);
17059         thinkOutput[0] = NULLCHAR;
17060         if (gameMode == TwoMachinesPlay) {
17061             SendToProgram("nopost\n", &second);
17062         }
17063     }
17064 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17065 }
17066
17067 void
17068 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17069 {
17070   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17071   if (pr == NoProc) return;
17072   AskQuestion(title, question, replyPrefix, pr);
17073 }
17074
17075 void
17076 TypeInEvent (char firstChar)
17077 {
17078     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17079         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17080         gameMode == AnalyzeMode || gameMode == EditGame ||
17081         gameMode == EditPosition || gameMode == IcsExamining ||
17082         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17083         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17084                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17085                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17086         gameMode == Training) PopUpMoveDialog(firstChar);
17087 }
17088
17089 void
17090 TypeInDoneEvent (char *move)
17091 {
17092         Board board;
17093         int n, fromX, fromY, toX, toY;
17094         char promoChar;
17095         ChessMove moveType;
17096
17097         // [HGM] FENedit
17098         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17099                 EditPositionPasteFEN(move);
17100                 return;
17101         }
17102         // [HGM] movenum: allow move number to be typed in any mode
17103         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17104           ToNrEvent(2*n-1);
17105           return;
17106         }
17107         // undocumented kludge: allow command-line option to be typed in!
17108         // (potentially fatal, and does not implement the effect of the option.)
17109         // should only be used for options that are values on which future decisions will be made,
17110         // and definitely not on options that would be used during initialization.
17111         if(strstr(move, "!!! -") == move) {
17112             ParseArgsFromString(move+4);
17113             return;
17114         }
17115
17116       if (gameMode != EditGame && currentMove != forwardMostMove &&
17117         gameMode != Training) {
17118         DisplayMoveError(_("Displayed move is not current"));
17119       } else {
17120         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17121           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17122         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17123         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17124           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17125           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17126         } else {
17127           DisplayMoveError(_("Could not parse move"));
17128         }
17129       }
17130 }
17131
17132 void
17133 DisplayMove (int moveNumber)
17134 {
17135     char message[MSG_SIZ];
17136     char res[MSG_SIZ];
17137     char cpThinkOutput[MSG_SIZ];
17138
17139     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17140
17141     if (moveNumber == forwardMostMove - 1 ||
17142         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17143
17144         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17145
17146         if (strchr(cpThinkOutput, '\n')) {
17147             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17148         }
17149     } else {
17150         *cpThinkOutput = NULLCHAR;
17151     }
17152
17153     /* [AS] Hide thinking from human user */
17154     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17155         *cpThinkOutput = NULLCHAR;
17156         if( thinkOutput[0] != NULLCHAR ) {
17157             int i;
17158
17159             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17160                 cpThinkOutput[i] = '.';
17161             }
17162             cpThinkOutput[i] = NULLCHAR;
17163             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17164         }
17165     }
17166
17167     if (moveNumber == forwardMostMove - 1 &&
17168         gameInfo.resultDetails != NULL) {
17169         if (gameInfo.resultDetails[0] == NULLCHAR) {
17170           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17171         } else {
17172           snprintf(res, MSG_SIZ, " {%s} %s",
17173                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17174         }
17175     } else {
17176         res[0] = NULLCHAR;
17177     }
17178
17179     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17180         DisplayMessage(res, cpThinkOutput);
17181     } else {
17182       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17183                 WhiteOnMove(moveNumber) ? " " : ".. ",
17184                 parseList[moveNumber], res);
17185         DisplayMessage(message, cpThinkOutput);
17186     }
17187 }
17188
17189 void
17190 DisplayComment (int moveNumber, char *text)
17191 {
17192     char title[MSG_SIZ];
17193
17194     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17195       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17196     } else {
17197       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17198               WhiteOnMove(moveNumber) ? " " : ".. ",
17199               parseList[moveNumber]);
17200     }
17201     if (text != NULL && (appData.autoDisplayComment || commentUp))
17202         CommentPopUp(title, text);
17203 }
17204
17205 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17206  * might be busy thinking or pondering.  It can be omitted if your
17207  * gnuchess is configured to stop thinking immediately on any user
17208  * input.  However, that gnuchess feature depends on the FIONREAD
17209  * ioctl, which does not work properly on some flavors of Unix.
17210  */
17211 void
17212 Attention (ChessProgramState *cps)
17213 {
17214 #if ATTENTION
17215     if (!cps->useSigint) return;
17216     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17217     switch (gameMode) {
17218       case MachinePlaysWhite:
17219       case MachinePlaysBlack:
17220       case TwoMachinesPlay:
17221       case IcsPlayingWhite:
17222       case IcsPlayingBlack:
17223       case AnalyzeMode:
17224       case AnalyzeFile:
17225         /* Skip if we know it isn't thinking */
17226         if (!cps->maybeThinking) return;
17227         if (appData.debugMode)
17228           fprintf(debugFP, "Interrupting %s\n", cps->which);
17229         InterruptChildProcess(cps->pr);
17230         cps->maybeThinking = FALSE;
17231         break;
17232       default:
17233         break;
17234     }
17235 #endif /*ATTENTION*/
17236 }
17237
17238 int
17239 CheckFlags ()
17240 {
17241     if (whiteTimeRemaining <= 0) {
17242         if (!whiteFlag) {
17243             whiteFlag = TRUE;
17244             if (appData.icsActive) {
17245                 if (appData.autoCallFlag &&
17246                     gameMode == IcsPlayingBlack && !blackFlag) {
17247                   SendToICS(ics_prefix);
17248                   SendToICS("flag\n");
17249                 }
17250             } else {
17251                 if (blackFlag) {
17252                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17253                 } else {
17254                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17255                     if (appData.autoCallFlag) {
17256                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17257                         return TRUE;
17258                     }
17259                 }
17260             }
17261         }
17262     }
17263     if (blackTimeRemaining <= 0) {
17264         if (!blackFlag) {
17265             blackFlag = TRUE;
17266             if (appData.icsActive) {
17267                 if (appData.autoCallFlag &&
17268                     gameMode == IcsPlayingWhite && !whiteFlag) {
17269                   SendToICS(ics_prefix);
17270                   SendToICS("flag\n");
17271                 }
17272             } else {
17273                 if (whiteFlag) {
17274                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17275                 } else {
17276                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17277                     if (appData.autoCallFlag) {
17278                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17279                         return TRUE;
17280                     }
17281                 }
17282             }
17283         }
17284     }
17285     return FALSE;
17286 }
17287
17288 void
17289 CheckTimeControl ()
17290 {
17291     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17292         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17293
17294     /*
17295      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17296      */
17297     if ( !WhiteOnMove(forwardMostMove) ) {
17298         /* White made time control */
17299         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17300         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17301         /* [HGM] time odds: correct new time quota for time odds! */
17302                                             / WhitePlayer()->timeOdds;
17303         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17304     } else {
17305         lastBlack -= blackTimeRemaining;
17306         /* Black made time control */
17307         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17308                                             / WhitePlayer()->other->timeOdds;
17309         lastWhite = whiteTimeRemaining;
17310     }
17311 }
17312
17313 void
17314 DisplayBothClocks ()
17315 {
17316     int wom = gameMode == EditPosition ?
17317       !blackPlaysFirst : WhiteOnMove(currentMove);
17318     DisplayWhiteClock(whiteTimeRemaining, wom);
17319     DisplayBlackClock(blackTimeRemaining, !wom);
17320 }
17321
17322
17323 /* Timekeeping seems to be a portability nightmare.  I think everyone
17324    has ftime(), but I'm really not sure, so I'm including some ifdefs
17325    to use other calls if you don't.  Clocks will be less accurate if
17326    you have neither ftime nor gettimeofday.
17327 */
17328
17329 /* VS 2008 requires the #include outside of the function */
17330 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17331 #include <sys/timeb.h>
17332 #endif
17333
17334 /* Get the current time as a TimeMark */
17335 void
17336 GetTimeMark (TimeMark *tm)
17337 {
17338 #if HAVE_GETTIMEOFDAY
17339
17340     struct timeval timeVal;
17341     struct timezone timeZone;
17342
17343     gettimeofday(&timeVal, &timeZone);
17344     tm->sec = (long) timeVal.tv_sec;
17345     tm->ms = (int) (timeVal.tv_usec / 1000L);
17346
17347 #else /*!HAVE_GETTIMEOFDAY*/
17348 #if HAVE_FTIME
17349
17350 // include <sys/timeb.h> / moved to just above start of function
17351     struct timeb timeB;
17352
17353     ftime(&timeB);
17354     tm->sec = (long) timeB.time;
17355     tm->ms = (int) timeB.millitm;
17356
17357 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17358     tm->sec = (long) time(NULL);
17359     tm->ms = 0;
17360 #endif
17361 #endif
17362 }
17363
17364 /* Return the difference in milliseconds between two
17365    time marks.  We assume the difference will fit in a long!
17366 */
17367 long
17368 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17369 {
17370     return 1000L*(tm2->sec - tm1->sec) +
17371            (long) (tm2->ms - tm1->ms);
17372 }
17373
17374
17375 /*
17376  * Code to manage the game clocks.
17377  *
17378  * In tournament play, black starts the clock and then white makes a move.
17379  * We give the human user a slight advantage if he is playing white---the
17380  * clocks don't run until he makes his first move, so it takes zero time.
17381  * Also, we don't account for network lag, so we could get out of sync
17382  * with GNU Chess's clock -- but then, referees are always right.
17383  */
17384
17385 static TimeMark tickStartTM;
17386 static long intendedTickLength;
17387
17388 long
17389 NextTickLength (long timeRemaining)
17390 {
17391     long nominalTickLength, nextTickLength;
17392
17393     if (timeRemaining > 0L && timeRemaining <= 10000L)
17394       nominalTickLength = 100L;
17395     else
17396       nominalTickLength = 1000L;
17397     nextTickLength = timeRemaining % nominalTickLength;
17398     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17399
17400     return nextTickLength;
17401 }
17402
17403 /* Adjust clock one minute up or down */
17404 void
17405 AdjustClock (Boolean which, int dir)
17406 {
17407     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17408     if(which) blackTimeRemaining += 60000*dir;
17409     else      whiteTimeRemaining += 60000*dir;
17410     DisplayBothClocks();
17411     adjustedClock = TRUE;
17412 }
17413
17414 /* Stop clocks and reset to a fresh time control */
17415 void
17416 ResetClocks ()
17417 {
17418     (void) StopClockTimer();
17419     if (appData.icsActive) {
17420         whiteTimeRemaining = blackTimeRemaining = 0;
17421     } else if (searchTime) {
17422         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17423         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17424     } else { /* [HGM] correct new time quote for time odds */
17425         whiteTC = blackTC = fullTimeControlString;
17426         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17427         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17428     }
17429     if (whiteFlag || blackFlag) {
17430         DisplayTitle("");
17431         whiteFlag = blackFlag = FALSE;
17432     }
17433     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17434     DisplayBothClocks();
17435     adjustedClock = FALSE;
17436 }
17437
17438 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17439
17440 /* Decrement running clock by amount of time that has passed */
17441 void
17442 DecrementClocks ()
17443 {
17444     long timeRemaining;
17445     long lastTickLength, fudge;
17446     TimeMark now;
17447
17448     if (!appData.clockMode) return;
17449     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17450
17451     GetTimeMark(&now);
17452
17453     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17454
17455     /* Fudge if we woke up a little too soon */
17456     fudge = intendedTickLength - lastTickLength;
17457     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17458
17459     if (WhiteOnMove(forwardMostMove)) {
17460         if(whiteNPS >= 0) lastTickLength = 0;
17461         timeRemaining = whiteTimeRemaining -= lastTickLength;
17462         if(timeRemaining < 0 && !appData.icsActive) {
17463             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17464             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17465                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17466                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17467             }
17468         }
17469         DisplayWhiteClock(whiteTimeRemaining - fudge,
17470                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17471     } else {
17472         if(blackNPS >= 0) lastTickLength = 0;
17473         timeRemaining = blackTimeRemaining -= lastTickLength;
17474         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17475             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17476             if(suddenDeath) {
17477                 blackStartMove = forwardMostMove;
17478                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17479             }
17480         }
17481         DisplayBlackClock(blackTimeRemaining - fudge,
17482                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17483     }
17484     if (CheckFlags()) return;
17485
17486     if(twoBoards) { // count down secondary board's clocks as well
17487         activePartnerTime -= lastTickLength;
17488         partnerUp = 1;
17489         if(activePartner == 'W')
17490             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17491         else
17492             DisplayBlackClock(activePartnerTime, TRUE);
17493         partnerUp = 0;
17494     }
17495
17496     tickStartTM = now;
17497     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17498     StartClockTimer(intendedTickLength);
17499
17500     /* if the time remaining has fallen below the alarm threshold, sound the
17501      * alarm. if the alarm has sounded and (due to a takeback or time control
17502      * with increment) the time remaining has increased to a level above the
17503      * threshold, reset the alarm so it can sound again.
17504      */
17505
17506     if (appData.icsActive && appData.icsAlarm) {
17507
17508         /* make sure we are dealing with the user's clock */
17509         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17510                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17511            )) return;
17512
17513         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17514             alarmSounded = FALSE;
17515         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17516             PlayAlarmSound();
17517             alarmSounded = TRUE;
17518         }
17519     }
17520 }
17521
17522
17523 /* A player has just moved, so stop the previously running
17524    clock and (if in clock mode) start the other one.
17525    We redisplay both clocks in case we're in ICS mode, because
17526    ICS gives us an update to both clocks after every move.
17527    Note that this routine is called *after* forwardMostMove
17528    is updated, so the last fractional tick must be subtracted
17529    from the color that is *not* on move now.
17530 */
17531 void
17532 SwitchClocks (int newMoveNr)
17533 {
17534     long lastTickLength;
17535     TimeMark now;
17536     int flagged = FALSE;
17537
17538     GetTimeMark(&now);
17539
17540     if (StopClockTimer() && appData.clockMode) {
17541         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17542         if (!WhiteOnMove(forwardMostMove)) {
17543             if(blackNPS >= 0) lastTickLength = 0;
17544             blackTimeRemaining -= lastTickLength;
17545            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17546 //         if(pvInfoList[forwardMostMove].time == -1)
17547                  pvInfoList[forwardMostMove].time =               // use GUI time
17548                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17549         } else {
17550            if(whiteNPS >= 0) lastTickLength = 0;
17551            whiteTimeRemaining -= lastTickLength;
17552            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17553 //         if(pvInfoList[forwardMostMove].time == -1)
17554                  pvInfoList[forwardMostMove].time =
17555                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17556         }
17557         flagged = CheckFlags();
17558     }
17559     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17560     CheckTimeControl();
17561
17562     if (flagged || !appData.clockMode) return;
17563
17564     switch (gameMode) {
17565       case MachinePlaysBlack:
17566       case MachinePlaysWhite:
17567       case BeginningOfGame:
17568         if (pausing) return;
17569         break;
17570
17571       case EditGame:
17572       case PlayFromGameFile:
17573       case IcsExamining:
17574         return;
17575
17576       default:
17577         break;
17578     }
17579
17580     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17581         if(WhiteOnMove(forwardMostMove))
17582              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17583         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17584     }
17585
17586     tickStartTM = now;
17587     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17588       whiteTimeRemaining : blackTimeRemaining);
17589     StartClockTimer(intendedTickLength);
17590 }
17591
17592
17593 /* Stop both clocks */
17594 void
17595 StopClocks ()
17596 {
17597     long lastTickLength;
17598     TimeMark now;
17599
17600     if (!StopClockTimer()) return;
17601     if (!appData.clockMode) return;
17602
17603     GetTimeMark(&now);
17604
17605     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17606     if (WhiteOnMove(forwardMostMove)) {
17607         if(whiteNPS >= 0) lastTickLength = 0;
17608         whiteTimeRemaining -= lastTickLength;
17609         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17610     } else {
17611         if(blackNPS >= 0) lastTickLength = 0;
17612         blackTimeRemaining -= lastTickLength;
17613         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17614     }
17615     CheckFlags();
17616 }
17617
17618 /* Start clock of player on move.  Time may have been reset, so
17619    if clock is already running, stop and restart it. */
17620 void
17621 StartClocks ()
17622 {
17623     (void) StopClockTimer(); /* in case it was running already */
17624     DisplayBothClocks();
17625     if (CheckFlags()) return;
17626
17627     if (!appData.clockMode) return;
17628     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17629
17630     GetTimeMark(&tickStartTM);
17631     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17632       whiteTimeRemaining : blackTimeRemaining);
17633
17634    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17635     whiteNPS = blackNPS = -1;
17636     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17637        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17638         whiteNPS = first.nps;
17639     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17640        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17641         blackNPS = first.nps;
17642     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17643         whiteNPS = second.nps;
17644     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17645         blackNPS = second.nps;
17646     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17647
17648     StartClockTimer(intendedTickLength);
17649 }
17650
17651 char *
17652 TimeString (long ms)
17653 {
17654     long second, minute, hour, day;
17655     char *sign = "";
17656     static char buf[32];
17657
17658     if (ms > 0 && ms <= 9900) {
17659       /* convert milliseconds to tenths, rounding up */
17660       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17661
17662       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17663       return buf;
17664     }
17665
17666     /* convert milliseconds to seconds, rounding up */
17667     /* use floating point to avoid strangeness of integer division
17668        with negative dividends on many machines */
17669     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17670
17671     if (second < 0) {
17672         sign = "-";
17673         second = -second;
17674     }
17675
17676     day = second / (60 * 60 * 24);
17677     second = second % (60 * 60 * 24);
17678     hour = second / (60 * 60);
17679     second = second % (60 * 60);
17680     minute = second / 60;
17681     second = second % 60;
17682
17683     if (day > 0)
17684       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17685               sign, day, hour, minute, second);
17686     else if (hour > 0)
17687       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17688     else
17689       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17690
17691     return buf;
17692 }
17693
17694
17695 /*
17696  * This is necessary because some C libraries aren't ANSI C compliant yet.
17697  */
17698 char *
17699 StrStr (char *string, char *match)
17700 {
17701     int i, length;
17702
17703     length = strlen(match);
17704
17705     for (i = strlen(string) - length; i >= 0; i--, string++)
17706       if (!strncmp(match, string, length))
17707         return string;
17708
17709     return NULL;
17710 }
17711
17712 char *
17713 StrCaseStr (char *string, char *match)
17714 {
17715     int i, j, length;
17716
17717     length = strlen(match);
17718
17719     for (i = strlen(string) - length; i >= 0; i--, string++) {
17720         for (j = 0; j < length; j++) {
17721             if (ToLower(match[j]) != ToLower(string[j]))
17722               break;
17723         }
17724         if (j == length) return string;
17725     }
17726
17727     return NULL;
17728 }
17729
17730 #ifndef _amigados
17731 int
17732 StrCaseCmp (char *s1, char *s2)
17733 {
17734     char c1, c2;
17735
17736     for (;;) {
17737         c1 = ToLower(*s1++);
17738         c2 = ToLower(*s2++);
17739         if (c1 > c2) return 1;
17740         if (c1 < c2) return -1;
17741         if (c1 == NULLCHAR) return 0;
17742     }
17743 }
17744
17745
17746 int
17747 ToLower (int c)
17748 {
17749     return isupper(c) ? tolower(c) : c;
17750 }
17751
17752
17753 int
17754 ToUpper (int c)
17755 {
17756     return islower(c) ? toupper(c) : c;
17757 }
17758 #endif /* !_amigados    */
17759
17760 char *
17761 StrSave (char *s)
17762 {
17763   char *ret;
17764
17765   if ((ret = (char *) malloc(strlen(s) + 1)))
17766     {
17767       safeStrCpy(ret, s, strlen(s)+1);
17768     }
17769   return ret;
17770 }
17771
17772 char *
17773 StrSavePtr (char *s, char **savePtr)
17774 {
17775     if (*savePtr) {
17776         free(*savePtr);
17777     }
17778     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17779       safeStrCpy(*savePtr, s, strlen(s)+1);
17780     }
17781     return(*savePtr);
17782 }
17783
17784 char *
17785 PGNDate ()
17786 {
17787     time_t clock;
17788     struct tm *tm;
17789     char buf[MSG_SIZ];
17790
17791     clock = time((time_t *)NULL);
17792     tm = localtime(&clock);
17793     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17794             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17795     return StrSave(buf);
17796 }
17797
17798
17799 char *
17800 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17801 {
17802     int i, j, fromX, fromY, toX, toY;
17803     int whiteToPlay;
17804     char buf[MSG_SIZ];
17805     char *p, *q;
17806     int emptycount;
17807     ChessSquare piece;
17808
17809     whiteToPlay = (gameMode == EditPosition) ?
17810       !blackPlaysFirst : (move % 2 == 0);
17811     p = buf;
17812
17813     /* Piece placement data */
17814     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17815         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17816         emptycount = 0;
17817         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17818             if (boards[move][i][j] == EmptySquare) {
17819                 emptycount++;
17820             } else { ChessSquare piece = boards[move][i][j];
17821                 if (emptycount > 0) {
17822                     if(emptycount<10) /* [HGM] can be >= 10 */
17823                         *p++ = '0' + emptycount;
17824                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17825                     emptycount = 0;
17826                 }
17827                 if(PieceToChar(piece) == '+') {
17828                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17829                     *p++ = '+';
17830                     piece = (ChessSquare)(CHUDEMOTED piece);
17831                 }
17832                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17833                 if(p[-1] == '~') {
17834                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17835                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17836                     *p++ = '~';
17837                 }
17838             }
17839         }
17840         if (emptycount > 0) {
17841             if(emptycount<10) /* [HGM] can be >= 10 */
17842                 *p++ = '0' + emptycount;
17843             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17844             emptycount = 0;
17845         }
17846         *p++ = '/';
17847     }
17848     *(p - 1) = ' ';
17849
17850     /* [HGM] print Crazyhouse or Shogi holdings */
17851     if( gameInfo.holdingsWidth ) {
17852         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17853         q = p;
17854         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17855             piece = boards[move][i][BOARD_WIDTH-1];
17856             if( piece != EmptySquare )
17857               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17858                   *p++ = PieceToChar(piece);
17859         }
17860         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17861             piece = boards[move][BOARD_HEIGHT-i-1][0];
17862             if( piece != EmptySquare )
17863               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17864                   *p++ = PieceToChar(piece);
17865         }
17866
17867         if( q == p ) *p++ = '-';
17868         *p++ = ']';
17869         *p++ = ' ';
17870     }
17871
17872     /* Active color */
17873     *p++ = whiteToPlay ? 'w' : 'b';
17874     *p++ = ' ';
17875
17876   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17877     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17878   } else {
17879   if(nrCastlingRights) {
17880      int handW=0, handB=0;
17881      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
17882         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
17883         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17884      }
17885      q = p;
17886      if(appData.fischerCastling) {
17887         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
17888            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17889                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17890         } else {
17891        /* [HGM] write directly from rights */
17892            if(boards[move][CASTLING][2] != NoRights &&
17893               boards[move][CASTLING][0] != NoRights   )
17894                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17895            if(boards[move][CASTLING][2] != NoRights &&
17896               boards[move][CASTLING][1] != NoRights   )
17897                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17898         }
17899         if(handB) {
17900            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17901                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17902         } else {
17903            if(boards[move][CASTLING][5] != NoRights &&
17904               boards[move][CASTLING][3] != NoRights   )
17905                 *p++ = boards[move][CASTLING][3] + AAA;
17906            if(boards[move][CASTLING][5] != NoRights &&
17907               boards[move][CASTLING][4] != NoRights   )
17908                 *p++ = boards[move][CASTLING][4] + AAA;
17909         }
17910      } else {
17911
17912         /* [HGM] write true castling rights */
17913         if( nrCastlingRights == 6 ) {
17914             int q, k=0;
17915             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17916                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17917             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17918                  boards[move][CASTLING][2] != NoRights  );
17919             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
17920                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17921                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17922                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17923             }
17924             if(q) *p++ = 'Q';
17925             k = 0;
17926             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17927                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17928             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17929                  boards[move][CASTLING][5] != NoRights  );
17930             if(handB) {
17931                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17932                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17933                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17934             }
17935             if(q) *p++ = 'q';
17936         }
17937      }
17938      if (q == p) *p++ = '-'; /* No castling rights */
17939      *p++ = ' ';
17940   }
17941
17942   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17943      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17944      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17945     /* En passant target square */
17946     if (move > backwardMostMove) {
17947         fromX = moveList[move - 1][0] - AAA;
17948         fromY = moveList[move - 1][1] - ONE;
17949         toX = moveList[move - 1][2] - AAA;
17950         toY = moveList[move - 1][3] - ONE;
17951         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17952             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17953             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17954             fromX == toX) {
17955             /* 2-square pawn move just happened */
17956             *p++ = toX + AAA;
17957             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17958         } else {
17959             *p++ = '-';
17960         }
17961     } else if(move == backwardMostMove) {
17962         // [HGM] perhaps we should always do it like this, and forget the above?
17963         if((signed char)boards[move][EP_STATUS] >= 0) {
17964             *p++ = boards[move][EP_STATUS] + AAA;
17965             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17966         } else {
17967             *p++ = '-';
17968         }
17969     } else {
17970         *p++ = '-';
17971     }
17972     *p++ = ' ';
17973   }
17974   }
17975
17976     if(moveCounts)
17977     {   int i = 0, j=move;
17978
17979         /* [HGM] find reversible plies */
17980         if (appData.debugMode) { int k;
17981             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17982             for(k=backwardMostMove; k<=forwardMostMove; k++)
17983                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17984
17985         }
17986
17987         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17988         if( j == backwardMostMove ) i += initialRulePlies;
17989         sprintf(p, "%d ", i);
17990         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17991
17992         /* Fullmove number */
17993         sprintf(p, "%d", (move / 2) + 1);
17994     } else *--p = NULLCHAR;
17995
17996     return StrSave(buf);
17997 }
17998
17999 Boolean
18000 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18001 {
18002     int i, j, k, w=0, subst=0, shuffle=0;
18003     char *p, c;
18004     int emptycount, virgin[BOARD_FILES];
18005     ChessSquare piece;
18006
18007     p = fen;
18008
18009     /* Piece placement data */
18010     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18011         j = 0;
18012         for (;;) {
18013             if (*p == '/' || *p == ' ' || *p == '[' ) {
18014                 if(j > w) w = j;
18015                 emptycount = gameInfo.boardWidth - j;
18016                 while (emptycount--)
18017                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18018                 if (*p == '/') p++;
18019                 else if(autoSize) { // we stumbled unexpectedly into end of board
18020                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18021                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18022                     }
18023                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18024                 }
18025                 break;
18026 #if(BOARD_FILES >= 10)*0
18027             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18028                 p++; emptycount=10;
18029                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18030                 while (emptycount--)
18031                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18032 #endif
18033             } else if (*p == '*') {
18034                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18035             } else if (isdigit(*p)) {
18036                 emptycount = *p++ - '0';
18037                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18038                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18039                 while (emptycount--)
18040                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18041             } else if (*p == '<') {
18042                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18043                 else if (i != 0 || !shuffle) return FALSE;
18044                 p++;
18045             } else if (shuffle && *p == '>') {
18046                 p++; // for now ignore closing shuffle range, and assume rank-end
18047             } else if (*p == '?') {
18048                 if (j >= gameInfo.boardWidth) return FALSE;
18049                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18050                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18051             } else if (*p == '+' || isalpha(*p)) {
18052                 if (j >= gameInfo.boardWidth) return FALSE;
18053                 if(*p=='+') {
18054                     piece = CharToPiece(*++p);
18055                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18056                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
18057                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18058                 } else piece = CharToPiece(*p++);
18059
18060                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18061                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18062                     piece = (ChessSquare) (PROMOTED piece);
18063                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18064                     p++;
18065                 }
18066                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18067             } else {
18068                 return FALSE;
18069             }
18070         }
18071     }
18072     while (*p == '/' || *p == ' ') p++;
18073
18074     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
18075
18076     /* [HGM] by default clear Crazyhouse holdings, if present */
18077     if(gameInfo.holdingsWidth) {
18078        for(i=0; i<BOARD_HEIGHT; i++) {
18079            board[i][0]             = EmptySquare; /* black holdings */
18080            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18081            board[i][1]             = (ChessSquare) 0; /* black counts */
18082            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18083        }
18084     }
18085
18086     /* [HGM] look for Crazyhouse holdings here */
18087     while(*p==' ') p++;
18088     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18089         int swap=0, wcnt=0, bcnt=0;
18090         if(*p == '[') p++;
18091         if(*p == '<') swap++, p++;
18092         if(*p == '-' ) p++; /* empty holdings */ else {
18093             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18094             /* if we would allow FEN reading to set board size, we would   */
18095             /* have to add holdings and shift the board read so far here   */
18096             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18097                 p++;
18098                 if((int) piece >= (int) BlackPawn ) {
18099                     i = (int)piece - (int)BlackPawn;
18100                     i = PieceToNumber((ChessSquare)i);
18101                     if( i >= gameInfo.holdingsSize ) return FALSE;
18102                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18103                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18104                     bcnt++;
18105                 } else {
18106                     i = (int)piece - (int)WhitePawn;
18107                     i = PieceToNumber((ChessSquare)i);
18108                     if( i >= gameInfo.holdingsSize ) return FALSE;
18109                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18110                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18111                     wcnt++;
18112                 }
18113             }
18114             if(subst) { // substitute back-rank question marks by holdings pieces
18115                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18116                     int k, m, n = bcnt + 1;
18117                     if(board[0][j] == ClearBoard) {
18118                         if(!wcnt) return FALSE;
18119                         n = rand() % wcnt;
18120                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18121                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18122                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18123                             break;
18124                         }
18125                     }
18126                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18127                         if(!bcnt) return FALSE;
18128                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18129                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18130                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18131                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18132                             break;
18133                         }
18134                     }
18135                 }
18136                 subst = 0;
18137             }
18138         }
18139         if(*p == ']') p++;
18140     }
18141
18142     if(subst) return FALSE; // substitution requested, but no holdings
18143
18144     while(*p == ' ') p++;
18145
18146     /* Active color */
18147     c = *p++;
18148     if(appData.colorNickNames) {
18149       if( c == appData.colorNickNames[0] ) c = 'w'; else
18150       if( c == appData.colorNickNames[1] ) c = 'b';
18151     }
18152     switch (c) {
18153       case 'w':
18154         *blackPlaysFirst = FALSE;
18155         break;
18156       case 'b':
18157         *blackPlaysFirst = TRUE;
18158         break;
18159       default:
18160         return FALSE;
18161     }
18162
18163     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18164     /* return the extra info in global variiables             */
18165
18166     /* set defaults in case FEN is incomplete */
18167     board[EP_STATUS] = EP_UNKNOWN;
18168     for(i=0; i<nrCastlingRights; i++ ) {
18169         board[CASTLING][i] =
18170             appData.fischerCastling ? NoRights : initialRights[i];
18171     }   /* assume possible unless obviously impossible */
18172     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18173     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18174     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18175                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18176     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18177     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18178     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18179                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18180     FENrulePlies = 0;
18181
18182     while(*p==' ') p++;
18183     if(nrCastlingRights) {
18184       int fischer = 0;
18185       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18186       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18187           /* castling indicator present, so default becomes no castlings */
18188           for(i=0; i<nrCastlingRights; i++ ) {
18189                  board[CASTLING][i] = NoRights;
18190           }
18191       }
18192       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18193              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18194              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18195              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18196         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18197
18198         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18199             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
18200             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
18201         }
18202         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18203             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18204         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18205                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18206         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18207                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18208         switch(c) {
18209           case'K':
18210               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
18211               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18212               board[CASTLING][2] = whiteKingFile;
18213               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18214               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18215               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18216               break;
18217           case'Q':
18218               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
18219               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18220               board[CASTLING][2] = whiteKingFile;
18221               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18222               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18223               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18224               break;
18225           case'k':
18226               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
18227               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18228               board[CASTLING][5] = blackKingFile;
18229               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18230               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18231               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18232               break;
18233           case'q':
18234               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18235               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18236               board[CASTLING][5] = blackKingFile;
18237               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18238               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18239               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18240           case '-':
18241               break;
18242           default: /* FRC castlings */
18243               if(c >= 'a') { /* black rights */
18244                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18245                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18246                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18247                   if(i == BOARD_RGHT) break;
18248                   board[CASTLING][5] = i;
18249                   c -= AAA;
18250                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18251                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18252                   if(c > i)
18253                       board[CASTLING][3] = c;
18254                   else
18255                       board[CASTLING][4] = c;
18256               } else { /* white rights */
18257                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18258                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18259                     if(board[0][i] == WhiteKing) break;
18260                   if(i == BOARD_RGHT) break;
18261                   board[CASTLING][2] = i;
18262                   c -= AAA - 'a' + 'A';
18263                   if(board[0][c] >= WhiteKing) break;
18264                   if(c > i)
18265                       board[CASTLING][0] = c;
18266                   else
18267                       board[CASTLING][1] = c;
18268               }
18269         }
18270       }
18271       for(i=0; i<nrCastlingRights; i++)
18272         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18273       if(gameInfo.variant == VariantSChess)
18274         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18275       if(fischer && shuffle) appData.fischerCastling = TRUE;
18276     if (appData.debugMode) {
18277         fprintf(debugFP, "FEN castling rights:");
18278         for(i=0; i<nrCastlingRights; i++)
18279         fprintf(debugFP, " %d", board[CASTLING][i]);
18280         fprintf(debugFP, "\n");
18281     }
18282
18283       while(*p==' ') p++;
18284     }
18285
18286     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18287
18288     /* read e.p. field in games that know e.p. capture */
18289     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18290        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18291        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18292       if(*p=='-') {
18293         p++; board[EP_STATUS] = EP_NONE;
18294       } else {
18295          char c = *p++ - AAA;
18296
18297          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18298          if(*p >= '0' && *p <='9') p++;
18299          board[EP_STATUS] = c;
18300       }
18301     }
18302
18303
18304     if(sscanf(p, "%d", &i) == 1) {
18305         FENrulePlies = i; /* 50-move ply counter */
18306         /* (The move number is still ignored)    */
18307     }
18308
18309     return TRUE;
18310 }
18311
18312 void
18313 EditPositionPasteFEN (char *fen)
18314 {
18315   if (fen != NULL) {
18316     Board initial_position;
18317
18318     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18319       DisplayError(_("Bad FEN position in clipboard"), 0);
18320       return ;
18321     } else {
18322       int savedBlackPlaysFirst = blackPlaysFirst;
18323       EditPositionEvent();
18324       blackPlaysFirst = savedBlackPlaysFirst;
18325       CopyBoard(boards[0], initial_position);
18326       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18327       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18328       DisplayBothClocks();
18329       DrawPosition(FALSE, boards[currentMove]);
18330     }
18331   }
18332 }
18333
18334 static char cseq[12] = "\\   ";
18335
18336 Boolean
18337 set_cont_sequence (char *new_seq)
18338 {
18339     int len;
18340     Boolean ret;
18341
18342     // handle bad attempts to set the sequence
18343         if (!new_seq)
18344                 return 0; // acceptable error - no debug
18345
18346     len = strlen(new_seq);
18347     ret = (len > 0) && (len < sizeof(cseq));
18348     if (ret)
18349       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18350     else if (appData.debugMode)
18351       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18352     return ret;
18353 }
18354
18355 /*
18356     reformat a source message so words don't cross the width boundary.  internal
18357     newlines are not removed.  returns the wrapped size (no null character unless
18358     included in source message).  If dest is NULL, only calculate the size required
18359     for the dest buffer.  lp argument indicats line position upon entry, and it's
18360     passed back upon exit.
18361 */
18362 int
18363 wrap (char *dest, char *src, int count, int width, int *lp)
18364 {
18365     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18366
18367     cseq_len = strlen(cseq);
18368     old_line = line = *lp;
18369     ansi = len = clen = 0;
18370
18371     for (i=0; i < count; i++)
18372     {
18373         if (src[i] == '\033')
18374             ansi = 1;
18375
18376         // if we hit the width, back up
18377         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18378         {
18379             // store i & len in case the word is too long
18380             old_i = i, old_len = len;
18381
18382             // find the end of the last word
18383             while (i && src[i] != ' ' && src[i] != '\n')
18384             {
18385                 i--;
18386                 len--;
18387             }
18388
18389             // word too long?  restore i & len before splitting it
18390             if ((old_i-i+clen) >= width)
18391             {
18392                 i = old_i;
18393                 len = old_len;
18394             }
18395
18396             // extra space?
18397             if (i && src[i-1] == ' ')
18398                 len--;
18399
18400             if (src[i] != ' ' && src[i] != '\n')
18401             {
18402                 i--;
18403                 if (len)
18404                     len--;
18405             }
18406
18407             // now append the newline and continuation sequence
18408             if (dest)
18409                 dest[len] = '\n';
18410             len++;
18411             if (dest)
18412                 strncpy(dest+len, cseq, cseq_len);
18413             len += cseq_len;
18414             line = cseq_len;
18415             clen = cseq_len;
18416             continue;
18417         }
18418
18419         if (dest)
18420             dest[len] = src[i];
18421         len++;
18422         if (!ansi)
18423             line++;
18424         if (src[i] == '\n')
18425             line = 0;
18426         if (src[i] == 'm')
18427             ansi = 0;
18428     }
18429     if (dest && appData.debugMode)
18430     {
18431         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18432             count, width, line, len, *lp);
18433         show_bytes(debugFP, src, count);
18434         fprintf(debugFP, "\ndest: ");
18435         show_bytes(debugFP, dest, len);
18436         fprintf(debugFP, "\n");
18437     }
18438     *lp = dest ? line : old_line;
18439
18440     return len;
18441 }
18442
18443 // [HGM] vari: routines for shelving variations
18444 Boolean modeRestore = FALSE;
18445
18446 void
18447 PushInner (int firstMove, int lastMove)
18448 {
18449         int i, j, nrMoves = lastMove - firstMove;
18450
18451         // push current tail of game on stack
18452         savedResult[storedGames] = gameInfo.result;
18453         savedDetails[storedGames] = gameInfo.resultDetails;
18454         gameInfo.resultDetails = NULL;
18455         savedFirst[storedGames] = firstMove;
18456         savedLast [storedGames] = lastMove;
18457         savedFramePtr[storedGames] = framePtr;
18458         framePtr -= nrMoves; // reserve space for the boards
18459         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18460             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18461             for(j=0; j<MOVE_LEN; j++)
18462                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18463             for(j=0; j<2*MOVE_LEN; j++)
18464                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18465             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18466             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18467             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18468             pvInfoList[firstMove+i-1].depth = 0;
18469             commentList[framePtr+i] = commentList[firstMove+i];
18470             commentList[firstMove+i] = NULL;
18471         }
18472
18473         storedGames++;
18474         forwardMostMove = firstMove; // truncate game so we can start variation
18475 }
18476
18477 void
18478 PushTail (int firstMove, int lastMove)
18479 {
18480         if(appData.icsActive) { // only in local mode
18481                 forwardMostMove = currentMove; // mimic old ICS behavior
18482                 return;
18483         }
18484         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18485
18486         PushInner(firstMove, lastMove);
18487         if(storedGames == 1) GreyRevert(FALSE);
18488         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18489 }
18490
18491 void
18492 PopInner (Boolean annotate)
18493 {
18494         int i, j, nrMoves;
18495         char buf[8000], moveBuf[20];
18496
18497         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18498         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18499         nrMoves = savedLast[storedGames] - currentMove;
18500         if(annotate) {
18501                 int cnt = 10;
18502                 if(!WhiteOnMove(currentMove))
18503                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18504                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18505                 for(i=currentMove; i<forwardMostMove; i++) {
18506                         if(WhiteOnMove(i))
18507                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18508                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18509                         strcat(buf, moveBuf);
18510                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18511                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18512                 }
18513                 strcat(buf, ")");
18514         }
18515         for(i=1; i<=nrMoves; i++) { // copy last variation back
18516             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18517             for(j=0; j<MOVE_LEN; j++)
18518                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18519             for(j=0; j<2*MOVE_LEN; j++)
18520                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18521             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18522             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18523             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18524             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18525             commentList[currentMove+i] = commentList[framePtr+i];
18526             commentList[framePtr+i] = NULL;
18527         }
18528         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18529         framePtr = savedFramePtr[storedGames];
18530         gameInfo.result = savedResult[storedGames];
18531         if(gameInfo.resultDetails != NULL) {
18532             free(gameInfo.resultDetails);
18533       }
18534         gameInfo.resultDetails = savedDetails[storedGames];
18535         forwardMostMove = currentMove + nrMoves;
18536 }
18537
18538 Boolean
18539 PopTail (Boolean annotate)
18540 {
18541         if(appData.icsActive) return FALSE; // only in local mode
18542         if(!storedGames) return FALSE; // sanity
18543         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18544
18545         PopInner(annotate);
18546         if(currentMove < forwardMostMove) ForwardEvent(); else
18547         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18548
18549         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18550         return TRUE;
18551 }
18552
18553 void
18554 CleanupTail ()
18555 {       // remove all shelved variations
18556         int i;
18557         for(i=0; i<storedGames; i++) {
18558             if(savedDetails[i])
18559                 free(savedDetails[i]);
18560             savedDetails[i] = NULL;
18561         }
18562         for(i=framePtr; i<MAX_MOVES; i++) {
18563                 if(commentList[i]) free(commentList[i]);
18564                 commentList[i] = NULL;
18565         }
18566         framePtr = MAX_MOVES-1;
18567         storedGames = 0;
18568 }
18569
18570 void
18571 LoadVariation (int index, char *text)
18572 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18573         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18574         int level = 0, move;
18575
18576         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18577         // first find outermost bracketing variation
18578         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18579             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18580                 if(*p == '{') wait = '}'; else
18581                 if(*p == '[') wait = ']'; else
18582                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18583                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18584             }
18585             if(*p == wait) wait = NULLCHAR; // closing ]} found
18586             p++;
18587         }
18588         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18589         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18590         end[1] = NULLCHAR; // clip off comment beyond variation
18591         ToNrEvent(currentMove-1);
18592         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18593         // kludge: use ParsePV() to append variation to game
18594         move = currentMove;
18595         ParsePV(start, TRUE, TRUE);
18596         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18597         ClearPremoveHighlights();
18598         CommentPopDown();
18599         ToNrEvent(currentMove+1);
18600 }
18601
18602 void
18603 LoadTheme ()
18604 {
18605     char *p, *q, buf[MSG_SIZ];
18606     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18607         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18608         ParseArgsFromString(buf);
18609         ActivateTheme(TRUE); // also redo colors
18610         return;
18611     }
18612     p = nickName;
18613     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18614     {
18615         int len;
18616         q = appData.themeNames;
18617         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18618       if(appData.useBitmaps) {
18619         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18620                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18621                 appData.liteBackTextureMode,
18622                 appData.darkBackTextureMode );
18623       } else {
18624         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18625                 Col2Text(2),   // lightSquareColor
18626                 Col2Text(3) ); // darkSquareColor
18627       }
18628       if(appData.useBorder) {
18629         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18630                 appData.border);
18631       } else {
18632         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18633       }
18634       if(appData.useFont) {
18635         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18636                 appData.renderPiecesWithFont,
18637                 appData.fontToPieceTable,
18638                 Col2Text(9),    // appData.fontBackColorWhite
18639                 Col2Text(10) ); // appData.fontForeColorBlack
18640       } else {
18641         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18642                 appData.pieceDirectory);
18643         if(!appData.pieceDirectory[0])
18644           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18645                 Col2Text(0),   // whitePieceColor
18646                 Col2Text(1) ); // blackPieceColor
18647       }
18648       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18649                 Col2Text(4),   // highlightSquareColor
18650                 Col2Text(5) ); // premoveHighlightColor
18651         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18652         if(insert != q) insert[-1] = NULLCHAR;
18653         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18654         if(q)   free(q);
18655     }
18656     ActivateTheme(FALSE);
18657 }