242d064e1375a3a5f5e1bd8cd0bde37535617a5d
[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 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 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152                          char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154                       char *buf, int count, int error));
155 void SendToICS P((char *s));
156 void SendToICSDelayed P((char *s, long msdelay));
157 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
158 void HandleMachineMove P((char *message, ChessProgramState *cps));
159 int AutoPlayOneMove P((void));
160 int LoadGameOneMove P((ChessMove readAhead));
161 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
162 int LoadPositionFromFile P((char *filename, int n, char *title));
163 int SavePositionToFile P((char *filename));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179                            char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181                         int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 int ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((int nr));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 int RegisterMove P((void));
199 void MakeRegisteredMove P((void));
200 void TruncateGame P((void));
201 int looking_at P((char *, int *, char *));
202 void CopyPlayerNameIntoFileName P((char **, char *));
203 char *SavePart P((char *));
204 int SaveGameOldStyle P((FILE *));
205 int SaveGamePGN P((FILE *));
206 int CheckFlags P((void));
207 long NextTickLength P((long));
208 void CheckTimeControl P((void));
209 void show_bytes P((FILE *, char *, int));
210 int string_to_rating P((char *str));
211 void ParseFeatures P((char* args, ChessProgramState *cps));
212 void InitBackEnd3 P((void));
213 void FeatureDone P((ChessProgramState* cps, int val));
214 void InitChessProgram P((ChessProgramState *cps, int setup));
215 void OutputKibitz(int window, char *text);
216 int PerpetualChase(int first, int last);
217 int EngineOutputIsUp();
218 void InitDrawingSizes(int x, int y);
219 void NextMatchGame P((void));
220 int NextTourneyGame P((int nr, int *swap));
221 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
222 FILE *WriteTourneyFile P((char *results, FILE *f));
223 void DisplayTwoMachinesTitle P(());
224 static void ExcludeClick P((int index));
225
226 #ifdef WIN32
227        extern void ConsoleCreate();
228 #endif
229
230 ChessProgramState *WhitePlayer();
231 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
232 int VerifyDisplayMode P(());
233
234 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
235 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
236 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
237 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
238 void ics_update_width P((int new_width));
239 extern char installDir[MSG_SIZ];
240 VariantClass startVariant; /* [HGM] nicks: initial variant */
241 Boolean abortMatch;
242
243 extern int tinyLayout, smallLayout;
244 ChessProgramStats programStats;
245 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
246 int endPV = -1;
247 static int exiting = 0; /* [HGM] moved to top */
248 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
249 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
250 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
251 int partnerHighlight[2];
252 Boolean partnerBoardValid = 0;
253 char partnerStatus[MSG_SIZ];
254 Boolean partnerUp;
255 Boolean originalFlip;
256 Boolean twoBoards = 0;
257 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
258 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
259 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
260 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
261 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
262 int opponentKibitzes;
263 int lastSavedGame; /* [HGM] save: ID of game */
264 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
265 extern int chatCount;
266 int chattingPartner;
267 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
268 char lastMsg[MSG_SIZ];
269 ChessSquare pieceSweep = EmptySquare;
270 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
271 int promoDefaultAltered;
272
273 /* States for ics_getting_history */
274 #define H_FALSE 0
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
280
281 /* whosays values for GameEnds */
282 #define GE_ICS 0
283 #define GE_ENGINE 1
284 #define GE_PLAYER 2
285 #define GE_FILE 3
286 #define GE_XBOARD 4
287 #define GE_ENGINE1 5
288 #define GE_ENGINE2 6
289
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
292
293 /* Different types of move when calling RegisterMove */
294 #define CMAIL_MOVE   0
295 #define CMAIL_RESIGN 1
296 #define CMAIL_DRAW   2
297 #define CMAIL_ACCEPT 3
298
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
303
304 /* Telnet protocol constants */
305 #define TN_WILL 0373
306 #define TN_WONT 0374
307 #define TN_DO   0375
308 #define TN_DONT 0376
309 #define TN_IAC  0377
310 #define TN_ECHO 0001
311 #define TN_SGA  0003
312 #define TN_PORT 23
313
314 char*
315 safeStrCpy (char *dst, const char *src, size_t count)
316 { // [HGM] made safe
317   int i;
318   assert( dst != NULL );
319   assert( src != NULL );
320   assert( count > 0 );
321
322   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323   if(  i == count && dst[count-1] != NULLCHAR)
324     {
325       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326       if(appData.debugMode)
327       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
328     }
329
330   return dst;
331 }
332
333 /* Some compiler can't cast u64 to double
334  * This function do the job for us:
335
336  * We use the highest bit for cast, this only
337  * works if the highest bit is not
338  * in use (This should not happen)
339  *
340  * We used this for all compiler
341  */
342 double
343 u64ToDouble (u64 value)
344 {
345   double r;
346   u64 tmp = value & u64Const(0x7fffffffffffffff);
347   r = (double)(s64)tmp;
348   if (value & u64Const(0x8000000000000000))
349        r +=  9.2233720368547758080e18; /* 2^63 */
350  return r;
351 }
352
353 /* Fake up flags for now, as we aren't keeping track of castling
354    availability yet. [HGM] Change of logic: the flag now only
355    indicates the type of castlings allowed by the rule of the game.
356    The actual rights themselves are maintained in the array
357    castlingRights, as part of the game history, and are not probed
358    by this function.
359  */
360 int
361 PosFlags (index)
362 {
363   int flags = F_ALL_CASTLE_OK;
364   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365   switch (gameInfo.variant) {
366   case VariantSuicide:
367     flags &= ~F_ALL_CASTLE_OK;
368   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369     flags |= F_IGNORE_CHECK;
370   case VariantLosers:
371     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
372     break;
373   case VariantAtomic:
374     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
375     break;
376   case VariantKriegspiel:
377     flags |= F_KRIEGSPIEL_CAPTURE;
378     break;
379   case VariantCapaRandom:
380   case VariantFischeRandom:
381     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382   case VariantNoCastle:
383   case VariantShatranj:
384   case VariantCourier:
385   case VariantMakruk:
386   case VariantGrand:
387     flags &= ~F_ALL_CASTLE_OK;
388     break;
389   default:
390     break;
391   }
392   return flags;
393 }
394
395 FILE *gameFileFP, *debugFP, *serverFP;
396 char *currentDebugFile; // [HGM] debug split: to remember name
397
398 /*
399     [AS] Note: sometimes, the sscanf() function is used to parse the input
400     into a fixed-size buffer. Because of this, we must be prepared to
401     receive strings as long as the size of the input buffer, which is currently
402     set to 4K for Windows and 8K for the rest.
403     So, we must either allocate sufficiently large buffers here, or
404     reduce the size of the input buffer in the input reading part.
405 */
406
407 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
408 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
409 char thinkOutput1[MSG_SIZ*10];
410
411 ChessProgramState first, second, pairing;
412
413 /* premove variables */
414 int premoveToX = 0;
415 int premoveToY = 0;
416 int premoveFromX = 0;
417 int premoveFromY = 0;
418 int premovePromoChar = 0;
419 int gotPremove = 0;
420 Boolean alarmSounded;
421 /* end premove variables */
422
423 char *ics_prefix = "$";
424 enum ICS_TYPE ics_type = ICS_GENERIC;
425
426 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
427 int pauseExamForwardMostMove = 0;
428 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
429 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
430 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
431 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
432 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
433 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
434 int whiteFlag = FALSE, blackFlag = FALSE;
435 int userOfferedDraw = FALSE;
436 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
437 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
438 int cmailMoveType[CMAIL_MAX_GAMES];
439 long ics_clock_paused = 0;
440 ProcRef icsPR = NoProc, cmailPR = NoProc;
441 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
442 GameMode gameMode = BeginningOfGame;
443 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
444 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
445 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
446 int hiddenThinkOutputState = 0; /* [AS] */
447 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
448 int adjudicateLossPlies = 6;
449 char white_holding[64], black_holding[64];
450 TimeMark lastNodeCountTime;
451 long lastNodeCount=0;
452 int shiftKey, controlKey; // [HGM] set by mouse handler
453
454 int have_sent_ICS_logon = 0;
455 int movesPerSession;
456 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
457 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
458 Boolean adjustedClock;
459 long timeControl_2; /* [AS] Allow separate time controls */
460 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
461 long timeRemaining[2][MAX_MOVES];
462 int matchGame = 0, nextGame = 0, roundNr = 0;
463 Boolean waitingForGame = FALSE;
464 TimeMark programStartTime, pauseStart;
465 char ics_handle[MSG_SIZ];
466 int have_set_title = 0;
467
468 /* animateTraining preserves the state of appData.animate
469  * when Training mode is activated. This allows the
470  * response to be animated when appData.animate == TRUE and
471  * appData.animateDragging == TRUE.
472  */
473 Boolean animateTraining;
474
475 GameInfo gameInfo;
476
477 AppData appData;
478
479 Board boards[MAX_MOVES];
480 /* [HGM] Following 7 needed for accurate legality tests: */
481 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
482 signed char  initialRights[BOARD_FILES];
483 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
484 int   initialRulePlies, FENrulePlies;
485 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
486 int loadFlag = 0;
487 Boolean shuffleOpenings;
488 int mute; // mute all sounds
489
490 // [HGM] vari: next 12 to save and restore variations
491 #define MAX_VARIATIONS 10
492 int framePtr = MAX_MOVES-1; // points to free stack entry
493 int storedGames = 0;
494 int savedFirst[MAX_VARIATIONS];
495 int savedLast[MAX_VARIATIONS];
496 int savedFramePtr[MAX_VARIATIONS];
497 char *savedDetails[MAX_VARIATIONS];
498 ChessMove savedResult[MAX_VARIATIONS];
499
500 void PushTail P((int firstMove, int lastMove));
501 Boolean PopTail P((Boolean annotate));
502 void PushInner P((int firstMove, int lastMove));
503 void PopInner P((Boolean annotate));
504 void CleanupTail P((void));
505
506 ChessSquare  FIDEArray[2][BOARD_FILES] = {
507     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
508         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
509     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
510         BlackKing, BlackBishop, BlackKnight, BlackRook }
511 };
512
513 ChessSquare twoKingsArray[2][BOARD_FILES] = {
514     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
516     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517         BlackKing, BlackKing, BlackKnight, BlackRook }
518 };
519
520 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
521     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
522         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
523     { BlackRook, BlackMan, BlackBishop, BlackQueen,
524         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
525 };
526
527 ChessSquare SpartanArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
529         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
530     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
531         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
532 };
533
534 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
538         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
539 };
540
541 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
542     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
543         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
544     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
545         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
546 };
547
548 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
550         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
551     { BlackRook, BlackKnight, BlackMan, BlackFerz,
552         BlackKing, BlackMan, BlackKnight, BlackRook }
553 };
554
555
556 #if (BOARD_FILES>=10)
557 ChessSquare ShogiArray[2][BOARD_FILES] = {
558     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
559         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
560     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
561         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
562 };
563
564 ChessSquare XiangqiArray[2][BOARD_FILES] = {
565     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
566         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
567     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
568         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
569 };
570
571 ChessSquare CapablancaArray[2][BOARD_FILES] = {
572     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
573         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
574     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
575         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
576 };
577
578 ChessSquare GreatArray[2][BOARD_FILES] = {
579     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
580         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
581     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
582         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
583 };
584
585 ChessSquare JanusArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
587         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
588     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
589         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
590 };
591
592 ChessSquare GrandArray[2][BOARD_FILES] = {
593     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
594         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
595     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
596         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
597 };
598
599 #ifdef GOTHIC
600 ChessSquare GothicArray[2][BOARD_FILES] = {
601     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
602         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
603     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
604         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
605 };
606 #else // !GOTHIC
607 #define GothicArray CapablancaArray
608 #endif // !GOTHIC
609
610 #ifdef FALCON
611 ChessSquare FalconArray[2][BOARD_FILES] = {
612     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
613         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
614     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
615         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
616 };
617 #else // !FALCON
618 #define FalconArray CapablancaArray
619 #endif // !FALCON
620
621 #else // !(BOARD_FILES>=10)
622 #define XiangqiPosition FIDEArray
623 #define CapablancaArray FIDEArray
624 #define GothicArray FIDEArray
625 #define GreatArray FIDEArray
626 #endif // !(BOARD_FILES>=10)
627
628 #if (BOARD_FILES>=12)
629 ChessSquare CourierArray[2][BOARD_FILES] = {
630     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
631         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
632     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
633         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
634 };
635 #else // !(BOARD_FILES>=12)
636 #define CourierArray CapablancaArray
637 #endif // !(BOARD_FILES>=12)
638
639
640 Board initialPosition;
641
642
643 /* Convert str to a rating. Checks for special cases of "----",
644
645    "++++", etc. Also strips ()'s */
646 int
647 string_to_rating (char *str)
648 {
649   while(*str && !isdigit(*str)) ++str;
650   if (!*str)
651     return 0;   /* One of the special "no rating" cases */
652   else
653     return atoi(str);
654 }
655
656 void
657 ClearProgramStats ()
658 {
659     /* Init programStats */
660     programStats.movelist[0] = 0;
661     programStats.depth = 0;
662     programStats.nr_moves = 0;
663     programStats.moves_left = 0;
664     programStats.nodes = 0;
665     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
666     programStats.score = 0;
667     programStats.got_only_move = 0;
668     programStats.got_fail = 0;
669     programStats.line_is_book = 0;
670 }
671
672 void
673 CommonEngineInit ()
674 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
675     if (appData.firstPlaysBlack) {
676         first.twoMachinesColor = "black\n";
677         second.twoMachinesColor = "white\n";
678     } else {
679         first.twoMachinesColor = "white\n";
680         second.twoMachinesColor = "black\n";
681     }
682
683     first.other = &second;
684     second.other = &first;
685
686     { float norm = 1;
687         if(appData.timeOddsMode) {
688             norm = appData.timeOdds[0];
689             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
690         }
691         first.timeOdds  = appData.timeOdds[0]/norm;
692         second.timeOdds = appData.timeOdds[1]/norm;
693     }
694
695     if(programVersion) free(programVersion);
696     if (appData.noChessProgram) {
697         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
698         sprintf(programVersion, "%s", PACKAGE_STRING);
699     } else {
700       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
701       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
702       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
703     }
704 }
705
706 void
707 UnloadEngine (ChessProgramState *cps)
708 {
709         /* Kill off first chess program */
710         if (cps->isr != NULL)
711           RemoveInputSource(cps->isr);
712         cps->isr = NULL;
713
714         if (cps->pr != NoProc) {
715             ExitAnalyzeMode();
716             DoSleep( appData.delayBeforeQuit );
717             SendToProgram("quit\n", cps);
718             DoSleep( appData.delayAfterQuit );
719             DestroyChildProcess(cps->pr, cps->useSigterm);
720         }
721         cps->pr = NoProc;
722         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
723 }
724
725 void
726 ClearOptions (ChessProgramState *cps)
727 {
728     int i;
729     cps->nrOptions = cps->comboCnt = 0;
730     for(i=0; i<MAX_OPTIONS; i++) {
731         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
732         cps->option[i].textValue = 0;
733     }
734 }
735
736 char *engineNames[] = {
737   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
738      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
739 N_("first"),
740   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
741      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
742 N_("second")
743 };
744
745 void
746 InitEngine (ChessProgramState *cps, int n)
747 {   // [HGM] all engine initialiation put in a function that does one engine
748
749     ClearOptions(cps);
750
751     cps->which = engineNames[n];
752     cps->maybeThinking = FALSE;
753     cps->pr = NoProc;
754     cps->isr = NULL;
755     cps->sendTime = 2;
756     cps->sendDrawOffers = 1;
757
758     cps->program = appData.chessProgram[n];
759     cps->host = appData.host[n];
760     cps->dir = appData.directory[n];
761     cps->initString = appData.engInitString[n];
762     cps->computerString = appData.computerString[n];
763     cps->useSigint  = TRUE;
764     cps->useSigterm = TRUE;
765     cps->reuse = appData.reuse[n];
766     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
767     cps->useSetboard = FALSE;
768     cps->useSAN = FALSE;
769     cps->usePing = FALSE;
770     cps->lastPing = 0;
771     cps->lastPong = 0;
772     cps->usePlayother = FALSE;
773     cps->useColors = TRUE;
774     cps->useUsermove = FALSE;
775     cps->sendICS = FALSE;
776     cps->sendName = appData.icsActive;
777     cps->sdKludge = FALSE;
778     cps->stKludge = FALSE;
779     TidyProgramName(cps->program, cps->host, cps->tidy);
780     cps->matchWins = 0;
781     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
782     cps->analysisSupport = 2; /* detect */
783     cps->analyzing = FALSE;
784     cps->initDone = FALSE;
785
786     /* New features added by Tord: */
787     cps->useFEN960 = FALSE;
788     cps->useOOCastle = TRUE;
789     /* End of new features added by Tord. */
790     cps->fenOverride  = appData.fenOverride[n];
791
792     /* [HGM] time odds: set factor for each machine */
793     cps->timeOdds  = appData.timeOdds[n];
794
795     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
796     cps->accumulateTC = appData.accumulateTC[n];
797     cps->maxNrOfSessions = 1;
798
799     /* [HGM] debug */
800     cps->debug = FALSE;
801
802     cps->supportsNPS = UNKNOWN;
803     cps->memSize = FALSE;
804     cps->maxCores = FALSE;
805     cps->egtFormats[0] = NULLCHAR;
806
807     /* [HGM] options */
808     cps->optionSettings  = appData.engOptions[n];
809
810     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
811     cps->isUCI = appData.isUCI[n]; /* [AS] */
812     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
813
814     if (appData.protocolVersion[n] > PROTOVER
815         || appData.protocolVersion[n] < 1)
816       {
817         char buf[MSG_SIZ];
818         int len;
819
820         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
821                        appData.protocolVersion[n]);
822         if( (len >= MSG_SIZ) && appData.debugMode )
823           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
824
825         DisplayFatalError(buf, 0, 2);
826       }
827     else
828       {
829         cps->protocolVersion = appData.protocolVersion[n];
830       }
831
832     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
833     ParseFeatures(appData.featureDefaults, cps);
834 }
835
836 ChessProgramState *savCps;
837
838 void
839 LoadEngine ()
840 {
841     int i;
842     if(WaitForEngine(savCps, LoadEngine)) return;
843     CommonEngineInit(); // recalculate time odds
844     if(gameInfo.variant != StringToVariant(appData.variant)) {
845         // we changed variant when loading the engine; this forces us to reset
846         Reset(TRUE, savCps != &first);
847         EditGameEvent(); // for consistency with other path, as Reset changes mode
848     }
849     InitChessProgram(savCps, FALSE);
850     SendToProgram("force\n", savCps);
851     DisplayMessage("", "");
852     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
853     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
854     ThawUI();
855     SetGNUMode();
856 }
857
858 void
859 ReplaceEngine (ChessProgramState *cps, int n)
860 {
861     EditGameEvent();
862     UnloadEngine(cps);
863     appData.noChessProgram = FALSE;
864     appData.clockMode = TRUE;
865     InitEngine(cps, n);
866     UpdateLogos(TRUE);
867     if(n) return; // only startup first engine immediately; second can wait
868     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
869     LoadEngine();
870 }
871
872 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
873 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
874
875 static char resetOptions[] = 
876         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
877         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
878         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
879         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
880
881 void
882 FloatToFront(char **list, char *engineLine)
883 {
884     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
885     int i=0;
886     if(appData.recentEngines <= 0) return;
887     TidyProgramName(engineLine, "localhost", tidy+1);
888     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
889     strncpy(buf+1, *list, MSG_SIZ-50);
890     if(p = strstr(buf, tidy)) { // tidy name appears in list
891         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
892         while(*p++ = *++q); // squeeze out
893     }
894     strcat(tidy, buf+1); // put list behind tidy name
895     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
896     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
897     ASSIGN(*list, tidy+1);
898 }
899
900 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
901
902 void
903 Load (ChessProgramState *cps, int i)
904 {
905     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
906     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
907         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
908         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
909         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
910         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
911         appData.firstProtocolVersion = PROTOVER;
912         ParseArgsFromString(buf);
913         SwapEngines(i);
914         ReplaceEngine(cps, i);
915         FloatToFront(&appData.recentEngineList, engineLine);
916         return;
917     }
918     p = engineName;
919     while(q = strchr(p, SLASH)) p = q+1;
920     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
921     if(engineDir[0] != NULLCHAR) {
922         ASSIGN(appData.directory[i], engineDir); p = engineName;
923     } else if(p != engineName) { // derive directory from engine path, when not given
924         p[-1] = 0;
925         ASSIGN(appData.directory[i], engineName);
926         p[-1] = SLASH;
927         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
928     } else { ASSIGN(appData.directory[i], "."); }
929     if(params[0]) {
930         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
931         snprintf(command, MSG_SIZ, "%s %s", p, params);
932         p = command;
933     }
934     ASSIGN(appData.chessProgram[i], p);
935     appData.isUCI[i] = isUCI;
936     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
937     appData.hasOwnBookUCI[i] = hasBook;
938     if(!nickName[0]) useNick = FALSE;
939     if(useNick) ASSIGN(appData.pgnName[i], nickName);
940     if(addToList) {
941         int len;
942         char quote;
943         q = firstChessProgramNames;
944         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
945         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
946         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
947                         quote, p, quote, appData.directory[i], 
948                         useNick ? " -fn \"" : "",
949                         useNick ? nickName : "",
950                         useNick ? "\"" : "",
951                         v1 ? " -firstProtocolVersion 1" : "",
952                         hasBook ? "" : " -fNoOwnBookUCI",
953                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
954                         storeVariant ? " -variant " : "",
955                         storeVariant ? VariantName(gameInfo.variant) : "");
956         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
957         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
958         if(insert != q) insert[-1] = NULLCHAR;
959         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
960         if(q)   free(q);
961         FloatToFront(&appData.recentEngineList, buf);
962     }
963     ReplaceEngine(cps, i);
964 }
965
966 void
967 InitTimeControls ()
968 {
969     int matched, min, sec;
970     /*
971      * Parse timeControl resource
972      */
973     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
974                           appData.movesPerSession)) {
975         char buf[MSG_SIZ];
976         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
977         DisplayFatalError(buf, 0, 2);
978     }
979
980     /*
981      * Parse searchTime resource
982      */
983     if (*appData.searchTime != NULLCHAR) {
984         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
985         if (matched == 1) {
986             searchTime = min * 60;
987         } else if (matched == 2) {
988             searchTime = min * 60 + sec;
989         } else {
990             char buf[MSG_SIZ];
991             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
992             DisplayFatalError(buf, 0, 2);
993         }
994     }
995 }
996
997 void
998 InitBackEnd1 ()
999 {
1000
1001     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1002     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1003
1004     GetTimeMark(&programStartTime);
1005     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1006     appData.seedBase = random() + (random()<<15);
1007     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1008
1009     ClearProgramStats();
1010     programStats.ok_to_send = 1;
1011     programStats.seen_stat = 0;
1012
1013     /*
1014      * Initialize game list
1015      */
1016     ListNew(&gameList);
1017
1018
1019     /*
1020      * Internet chess server status
1021      */
1022     if (appData.icsActive) {
1023         appData.matchMode = FALSE;
1024         appData.matchGames = 0;
1025 #if ZIPPY
1026         appData.noChessProgram = !appData.zippyPlay;
1027 #else
1028         appData.zippyPlay = FALSE;
1029         appData.zippyTalk = FALSE;
1030         appData.noChessProgram = TRUE;
1031 #endif
1032         if (*appData.icsHelper != NULLCHAR) {
1033             appData.useTelnet = TRUE;
1034             appData.telnetProgram = appData.icsHelper;
1035         }
1036     } else {
1037         appData.zippyTalk = appData.zippyPlay = FALSE;
1038     }
1039
1040     /* [AS] Initialize pv info list [HGM] and game state */
1041     {
1042         int i, j;
1043
1044         for( i=0; i<=framePtr; i++ ) {
1045             pvInfoList[i].depth = -1;
1046             boards[i][EP_STATUS] = EP_NONE;
1047             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1048         }
1049     }
1050
1051     InitTimeControls();
1052
1053     /* [AS] Adjudication threshold */
1054     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1055
1056     InitEngine(&first, 0);
1057     InitEngine(&second, 1);
1058     CommonEngineInit();
1059
1060     pairing.which = "pairing"; // pairing engine
1061     pairing.pr = NoProc;
1062     pairing.isr = NULL;
1063     pairing.program = appData.pairingEngine;
1064     pairing.host = "localhost";
1065     pairing.dir = ".";
1066
1067     if (appData.icsActive) {
1068         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1069     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1070         appData.clockMode = FALSE;
1071         first.sendTime = second.sendTime = 0;
1072     }
1073
1074 #if ZIPPY
1075     /* Override some settings from environment variables, for backward
1076        compatibility.  Unfortunately it's not feasible to have the env
1077        vars just set defaults, at least in xboard.  Ugh.
1078     */
1079     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1080       ZippyInit();
1081     }
1082 #endif
1083
1084     if (!appData.icsActive) {
1085       char buf[MSG_SIZ];
1086       int len;
1087
1088       /* Check for variants that are supported only in ICS mode,
1089          or not at all.  Some that are accepted here nevertheless
1090          have bugs; see comments below.
1091       */
1092       VariantClass variant = StringToVariant(appData.variant);
1093       switch (variant) {
1094       case VariantBughouse:     /* need four players and two boards */
1095       case VariantKriegspiel:   /* need to hide pieces and move details */
1096         /* case VariantFischeRandom: (Fabien: moved below) */
1097         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1098         if( (len >= MSG_SIZ) && appData.debugMode )
1099           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1100
1101         DisplayFatalError(buf, 0, 2);
1102         return;
1103
1104       case VariantUnknown:
1105       case VariantLoadable:
1106       case Variant29:
1107       case Variant30:
1108       case Variant31:
1109       case Variant32:
1110       case Variant33:
1111       case Variant34:
1112       case Variant35:
1113       case Variant36:
1114       default:
1115         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1116         if( (len >= MSG_SIZ) && appData.debugMode )
1117           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1118
1119         DisplayFatalError(buf, 0, 2);
1120         return;
1121
1122       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1123       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1124       case VariantGothic:     /* [HGM] should work */
1125       case VariantCapablanca: /* [HGM] should work */
1126       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1127       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1128       case VariantKnightmate: /* [HGM] should work */
1129       case VariantCylinder:   /* [HGM] untested */
1130       case VariantFalcon:     /* [HGM] untested */
1131       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1132                                  offboard interposition not understood */
1133       case VariantNormal:     /* definitely works! */
1134       case VariantWildCastle: /* pieces not automatically shuffled */
1135       case VariantNoCastle:   /* pieces not automatically shuffled */
1136       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1137       case VariantLosers:     /* should work except for win condition,
1138                                  and doesn't know captures are mandatory */
1139       case VariantSuicide:    /* should work except for win condition,
1140                                  and doesn't know captures are mandatory */
1141       case VariantGiveaway:   /* should work except for win condition,
1142                                  and doesn't know captures are mandatory */
1143       case VariantTwoKings:   /* should work */
1144       case VariantAtomic:     /* should work except for win condition */
1145       case Variant3Check:     /* should work except for win condition */
1146       case VariantShatranj:   /* should work except for all win conditions */
1147       case VariantMakruk:     /* should work except for draw countdown */
1148       case VariantBerolina:   /* might work if TestLegality is off */
1149       case VariantCapaRandom: /* should work */
1150       case VariantJanus:      /* should work */
1151       case VariantSuper:      /* experimental */
1152       case VariantGreat:      /* experimental, requires legality testing to be off */
1153       case VariantSChess:     /* S-Chess, should work */
1154       case VariantGrand:      /* should work */
1155       case VariantSpartan:    /* should work */
1156         break;
1157       }
1158     }
1159
1160 }
1161
1162 int
1163 NextIntegerFromString (char ** str, long * value)
1164 {
1165     int result = -1;
1166     char * s = *str;
1167
1168     while( *s == ' ' || *s == '\t' ) {
1169         s++;
1170     }
1171
1172     *value = 0;
1173
1174     if( *s >= '0' && *s <= '9' ) {
1175         while( *s >= '0' && *s <= '9' ) {
1176             *value = *value * 10 + (*s - '0');
1177             s++;
1178         }
1179
1180         result = 0;
1181     }
1182
1183     *str = s;
1184
1185     return result;
1186 }
1187
1188 int
1189 NextTimeControlFromString (char ** str, long * value)
1190 {
1191     long temp;
1192     int result = NextIntegerFromString( str, &temp );
1193
1194     if( result == 0 ) {
1195         *value = temp * 60; /* Minutes */
1196         if( **str == ':' ) {
1197             (*str)++;
1198             result = NextIntegerFromString( str, &temp );
1199             *value += temp; /* Seconds */
1200         }
1201     }
1202
1203     return result;
1204 }
1205
1206 int
1207 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1208 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1209     int result = -1, type = 0; long temp, temp2;
1210
1211     if(**str != ':') return -1; // old params remain in force!
1212     (*str)++;
1213     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1214     if( NextIntegerFromString( str, &temp ) ) return -1;
1215     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1216
1217     if(**str != '/') {
1218         /* time only: incremental or sudden-death time control */
1219         if(**str == '+') { /* increment follows; read it */
1220             (*str)++;
1221             if(**str == '!') type = *(*str)++; // Bronstein TC
1222             if(result = NextIntegerFromString( str, &temp2)) return -1;
1223             *inc = temp2 * 1000;
1224             if(**str == '.') { // read fraction of increment
1225                 char *start = ++(*str);
1226                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1227                 temp2 *= 1000;
1228                 while(start++ < *str) temp2 /= 10;
1229                 *inc += temp2;
1230             }
1231         } else *inc = 0;
1232         *moves = 0; *tc = temp * 1000; *incType = type;
1233         return 0;
1234     }
1235
1236     (*str)++; /* classical time control */
1237     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1238
1239     if(result == 0) {
1240         *moves = temp;
1241         *tc    = temp2 * 1000;
1242         *inc   = 0;
1243         *incType = type;
1244     }
1245     return result;
1246 }
1247
1248 int
1249 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1250 {   /* [HGM] get time to add from the multi-session time-control string */
1251     int incType, moves=1; /* kludge to force reading of first session */
1252     long time, increment;
1253     char *s = tcString;
1254
1255     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1256     do {
1257         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1258         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1259         if(movenr == -1) return time;    /* last move before new session     */
1260         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1261         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1262         if(!moves) return increment;     /* current session is incremental   */
1263         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1264     } while(movenr >= -1);               /* try again for next session       */
1265
1266     return 0; // no new time quota on this move
1267 }
1268
1269 int
1270 ParseTimeControl (char *tc, float ti, int mps)
1271 {
1272   long tc1;
1273   long tc2;
1274   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1275   int min, sec=0;
1276
1277   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1278   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1279       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1280   if(ti > 0) {
1281
1282     if(mps)
1283       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1284     else 
1285       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1286   } else {
1287     if(mps)
1288       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1289     else 
1290       snprintf(buf, MSG_SIZ, ":%s", mytc);
1291   }
1292   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1293   
1294   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1295     return FALSE;
1296   }
1297
1298   if( *tc == '/' ) {
1299     /* Parse second time control */
1300     tc++;
1301
1302     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1303       return FALSE;
1304     }
1305
1306     if( tc2 == 0 ) {
1307       return FALSE;
1308     }
1309
1310     timeControl_2 = tc2 * 1000;
1311   }
1312   else {
1313     timeControl_2 = 0;
1314   }
1315
1316   if( tc1 == 0 ) {
1317     return FALSE;
1318   }
1319
1320   timeControl = tc1 * 1000;
1321
1322   if (ti >= 0) {
1323     timeIncrement = ti * 1000;  /* convert to ms */
1324     movesPerSession = 0;
1325   } else {
1326     timeIncrement = 0;
1327     movesPerSession = mps;
1328   }
1329   return TRUE;
1330 }
1331
1332 void
1333 InitBackEnd2 ()
1334 {
1335     if (appData.debugMode) {
1336         fprintf(debugFP, "%s\n", programVersion);
1337     }
1338     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1339
1340     set_cont_sequence(appData.wrapContSeq);
1341     if (appData.matchGames > 0) {
1342         appData.matchMode = TRUE;
1343     } else if (appData.matchMode) {
1344         appData.matchGames = 1;
1345     }
1346     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1347         appData.matchGames = appData.sameColorGames;
1348     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1349         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1350         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1351     }
1352     Reset(TRUE, FALSE);
1353     if (appData.noChessProgram || first.protocolVersion == 1) {
1354       InitBackEnd3();
1355     } else {
1356       /* kludge: allow timeout for initial "feature" commands */
1357       FreezeUI();
1358       DisplayMessage("", _("Starting chess program"));
1359       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1360     }
1361 }
1362
1363 int
1364 CalculateIndex (int index, int gameNr)
1365 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1366     int res;
1367     if(index > 0) return index; // fixed nmber
1368     if(index == 0) return 1;
1369     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1370     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1371     return res;
1372 }
1373
1374 int
1375 LoadGameOrPosition (int gameNr)
1376 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1377     if (*appData.loadGameFile != NULLCHAR) {
1378         if (!LoadGameFromFile(appData.loadGameFile,
1379                 CalculateIndex(appData.loadGameIndex, gameNr),
1380                               appData.loadGameFile, FALSE)) {
1381             DisplayFatalError(_("Bad game file"), 0, 1);
1382             return 0;
1383         }
1384     } else if (*appData.loadPositionFile != NULLCHAR) {
1385         if (!LoadPositionFromFile(appData.loadPositionFile,
1386                 CalculateIndex(appData.loadPositionIndex, gameNr),
1387                                   appData.loadPositionFile)) {
1388             DisplayFatalError(_("Bad position file"), 0, 1);
1389             return 0;
1390         }
1391     }
1392     return 1;
1393 }
1394
1395 void
1396 ReserveGame (int gameNr, char resChar)
1397 {
1398     FILE *tf = fopen(appData.tourneyFile, "r+");
1399     char *p, *q, c, buf[MSG_SIZ];
1400     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1401     safeStrCpy(buf, lastMsg, MSG_SIZ);
1402     DisplayMessage(_("Pick new game"), "");
1403     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1404     ParseArgsFromFile(tf);
1405     p = q = appData.results;
1406     if(appData.debugMode) {
1407       char *r = appData.participants;
1408       fprintf(debugFP, "results = '%s'\n", p);
1409       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1410       fprintf(debugFP, "\n");
1411     }
1412     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1413     nextGame = q - p;
1414     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1415     safeStrCpy(q, p, strlen(p) + 2);
1416     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1417     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1418     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1419         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1420         q[nextGame] = '*';
1421     }
1422     fseek(tf, -(strlen(p)+4), SEEK_END);
1423     c = fgetc(tf);
1424     if(c != '"') // depending on DOS or Unix line endings we can be one off
1425          fseek(tf, -(strlen(p)+2), SEEK_END);
1426     else fseek(tf, -(strlen(p)+3), SEEK_END);
1427     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1428     DisplayMessage(buf, "");
1429     free(p); appData.results = q;
1430     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1431        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1432       int round = appData.defaultMatchGames * appData.tourneyType;
1433       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1434          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1435         UnloadEngine(&first);  // next game belongs to other pairing;
1436         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1437     }
1438     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1439 }
1440
1441 void
1442 MatchEvent (int mode)
1443 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1444         int dummy;
1445         if(matchMode) { // already in match mode: switch it off
1446             abortMatch = TRUE;
1447             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1448             return;
1449         }
1450 //      if(gameMode != BeginningOfGame) {
1451 //          DisplayError(_("You can only start a match from the initial position."), 0);
1452 //          return;
1453 //      }
1454         abortMatch = FALSE;
1455         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1456         /* Set up machine vs. machine match */
1457         nextGame = 0;
1458         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1459         if(appData.tourneyFile[0]) {
1460             ReserveGame(-1, 0);
1461             if(nextGame > appData.matchGames) {
1462                 char buf[MSG_SIZ];
1463                 if(strchr(appData.results, '*') == NULL) {
1464                     FILE *f;
1465                     appData.tourneyCycles++;
1466                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1467                         fclose(f);
1468                         NextTourneyGame(-1, &dummy);
1469                         ReserveGame(-1, 0);
1470                         if(nextGame <= appData.matchGames) {
1471                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1472                             matchMode = mode;
1473                             ScheduleDelayedEvent(NextMatchGame, 10000);
1474                             return;
1475                         }
1476                     }
1477                 }
1478                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1479                 DisplayError(buf, 0);
1480                 appData.tourneyFile[0] = 0;
1481                 return;
1482             }
1483         } else
1484         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1485             DisplayFatalError(_("Can't have a match with no chess programs"),
1486                               0, 2);
1487             return;
1488         }
1489         matchMode = mode;
1490         matchGame = roundNr = 1;
1491         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1492         NextMatchGame();
1493 }
1494
1495 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1496
1497 void
1498 InitBackEnd3 P((void))
1499 {
1500     GameMode initialMode;
1501     char buf[MSG_SIZ];
1502     int err, len;
1503
1504     InitChessProgram(&first, startedFromSetupPosition);
1505
1506     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1507         free(programVersion);
1508         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1509         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1510         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1511     }
1512
1513     if (appData.icsActive) {
1514 #ifdef WIN32
1515         /* [DM] Make a console window if needed [HGM] merged ifs */
1516         ConsoleCreate();
1517 #endif
1518         err = establish();
1519         if (err != 0)
1520           {
1521             if (*appData.icsCommPort != NULLCHAR)
1522               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1523                              appData.icsCommPort);
1524             else
1525               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1526                         appData.icsHost, appData.icsPort);
1527
1528             if( (len >= MSG_SIZ) && appData.debugMode )
1529               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1530
1531             DisplayFatalError(buf, err, 1);
1532             return;
1533         }
1534         SetICSMode();
1535         telnetISR =
1536           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1537         fromUserISR =
1538           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1539         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1540             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1541     } else if (appData.noChessProgram) {
1542         SetNCPMode();
1543     } else {
1544         SetGNUMode();
1545     }
1546
1547     if (*appData.cmailGameName != NULLCHAR) {
1548         SetCmailMode();
1549         OpenLoopback(&cmailPR);
1550         cmailISR =
1551           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1552     }
1553
1554     ThawUI();
1555     DisplayMessage("", "");
1556     if (StrCaseCmp(appData.initialMode, "") == 0) {
1557       initialMode = BeginningOfGame;
1558       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1559         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1560         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1561         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1562         ModeHighlight();
1563       }
1564     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1565       initialMode = TwoMachinesPlay;
1566     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1567       initialMode = AnalyzeFile;
1568     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1569       initialMode = AnalyzeMode;
1570     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1571       initialMode = MachinePlaysWhite;
1572     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1573       initialMode = MachinePlaysBlack;
1574     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1575       initialMode = EditGame;
1576     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1577       initialMode = EditPosition;
1578     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1579       initialMode = Training;
1580     } else {
1581       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1582       if( (len >= MSG_SIZ) && appData.debugMode )
1583         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1584
1585       DisplayFatalError(buf, 0, 2);
1586       return;
1587     }
1588
1589     if (appData.matchMode) {
1590         if(appData.tourneyFile[0]) { // start tourney from command line
1591             FILE *f;
1592             if(f = fopen(appData.tourneyFile, "r")) {
1593                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1594                 fclose(f);
1595                 appData.clockMode = TRUE;
1596                 SetGNUMode();
1597             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1598         }
1599         MatchEvent(TRUE);
1600     } else if (*appData.cmailGameName != NULLCHAR) {
1601         /* Set up cmail mode */
1602         ReloadCmailMsgEvent(TRUE);
1603     } else {
1604         /* Set up other modes */
1605         if (initialMode == AnalyzeFile) {
1606           if (*appData.loadGameFile == NULLCHAR) {
1607             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1608             return;
1609           }
1610         }
1611         if (*appData.loadGameFile != NULLCHAR) {
1612             (void) LoadGameFromFile(appData.loadGameFile,
1613                                     appData.loadGameIndex,
1614                                     appData.loadGameFile, TRUE);
1615         } else if (*appData.loadPositionFile != NULLCHAR) {
1616             (void) LoadPositionFromFile(appData.loadPositionFile,
1617                                         appData.loadPositionIndex,
1618                                         appData.loadPositionFile);
1619             /* [HGM] try to make self-starting even after FEN load */
1620             /* to allow automatic setup of fairy variants with wtm */
1621             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1622                 gameMode = BeginningOfGame;
1623                 setboardSpoiledMachineBlack = 1;
1624             }
1625             /* [HGM] loadPos: make that every new game uses the setup */
1626             /* from file as long as we do not switch variant          */
1627             if(!blackPlaysFirst) {
1628                 startedFromPositionFile = TRUE;
1629                 CopyBoard(filePosition, boards[0]);
1630             }
1631         }
1632         if (initialMode == AnalyzeMode) {
1633           if (appData.noChessProgram) {
1634             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1635             return;
1636           }
1637           if (appData.icsActive) {
1638             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1639             return;
1640           }
1641           AnalyzeModeEvent();
1642         } else if (initialMode == AnalyzeFile) {
1643           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1644           ShowThinkingEvent();
1645           AnalyzeFileEvent();
1646           AnalysisPeriodicEvent(1);
1647         } else if (initialMode == MachinePlaysWhite) {
1648           if (appData.noChessProgram) {
1649             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1650                               0, 2);
1651             return;
1652           }
1653           if (appData.icsActive) {
1654             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1655                               0, 2);
1656             return;
1657           }
1658           MachineWhiteEvent();
1659         } else if (initialMode == MachinePlaysBlack) {
1660           if (appData.noChessProgram) {
1661             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1662                               0, 2);
1663             return;
1664           }
1665           if (appData.icsActive) {
1666             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1667                               0, 2);
1668             return;
1669           }
1670           MachineBlackEvent();
1671         } else if (initialMode == TwoMachinesPlay) {
1672           if (appData.noChessProgram) {
1673             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1674                               0, 2);
1675             return;
1676           }
1677           if (appData.icsActive) {
1678             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1679                               0, 2);
1680             return;
1681           }
1682           TwoMachinesEvent();
1683         } else if (initialMode == EditGame) {
1684           EditGameEvent();
1685         } else if (initialMode == EditPosition) {
1686           EditPositionEvent();
1687         } else if (initialMode == Training) {
1688           if (*appData.loadGameFile == NULLCHAR) {
1689             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1690             return;
1691           }
1692           TrainingEvent();
1693         }
1694     }
1695 }
1696
1697 void
1698 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1699 {
1700     DisplayBook(current+1);
1701
1702     MoveHistorySet( movelist, first, last, current, pvInfoList );
1703
1704     EvalGraphSet( first, last, current, pvInfoList );
1705
1706     MakeEngineOutputTitle();
1707 }
1708
1709 /*
1710  * Establish will establish a contact to a remote host.port.
1711  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1712  *  used to talk to the host.
1713  * Returns 0 if okay, error code if not.
1714  */
1715 int
1716 establish ()
1717 {
1718     char buf[MSG_SIZ];
1719
1720     if (*appData.icsCommPort != NULLCHAR) {
1721         /* Talk to the host through a serial comm port */
1722         return OpenCommPort(appData.icsCommPort, &icsPR);
1723
1724     } else if (*appData.gateway != NULLCHAR) {
1725         if (*appData.remoteShell == NULLCHAR) {
1726             /* Use the rcmd protocol to run telnet program on a gateway host */
1727             snprintf(buf, sizeof(buf), "%s %s %s",
1728                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1729             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1730
1731         } else {
1732             /* Use the rsh program to run telnet program on a gateway host */
1733             if (*appData.remoteUser == NULLCHAR) {
1734                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1735                         appData.gateway, appData.telnetProgram,
1736                         appData.icsHost, appData.icsPort);
1737             } else {
1738                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1739                         appData.remoteShell, appData.gateway,
1740                         appData.remoteUser, appData.telnetProgram,
1741                         appData.icsHost, appData.icsPort);
1742             }
1743             return StartChildProcess(buf, "", &icsPR);
1744
1745         }
1746     } else if (appData.useTelnet) {
1747         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1748
1749     } else {
1750         /* TCP socket interface differs somewhat between
1751            Unix and NT; handle details in the front end.
1752            */
1753         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1754     }
1755 }
1756
1757 void
1758 EscapeExpand (char *p, char *q)
1759 {       // [HGM] initstring: routine to shape up string arguments
1760         while(*p++ = *q++) if(p[-1] == '\\')
1761             switch(*q++) {
1762                 case 'n': p[-1] = '\n'; break;
1763                 case 'r': p[-1] = '\r'; break;
1764                 case 't': p[-1] = '\t'; break;
1765                 case '\\': p[-1] = '\\'; break;
1766                 case 0: *p = 0; return;
1767                 default: p[-1] = q[-1]; break;
1768             }
1769 }
1770
1771 void
1772 show_bytes (FILE *fp, char *buf, int count)
1773 {
1774     while (count--) {
1775         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1776             fprintf(fp, "\\%03o", *buf & 0xff);
1777         } else {
1778             putc(*buf, fp);
1779         }
1780         buf++;
1781     }
1782     fflush(fp);
1783 }
1784
1785 /* Returns an errno value */
1786 int
1787 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1788 {
1789     char buf[8192], *p, *q, *buflim;
1790     int left, newcount, outcount;
1791
1792     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1793         *appData.gateway != NULLCHAR) {
1794         if (appData.debugMode) {
1795             fprintf(debugFP, ">ICS: ");
1796             show_bytes(debugFP, message, count);
1797             fprintf(debugFP, "\n");
1798         }
1799         return OutputToProcess(pr, message, count, outError);
1800     }
1801
1802     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1803     p = message;
1804     q = buf;
1805     left = count;
1806     newcount = 0;
1807     while (left) {
1808         if (q >= buflim) {
1809             if (appData.debugMode) {
1810                 fprintf(debugFP, ">ICS: ");
1811                 show_bytes(debugFP, buf, newcount);
1812                 fprintf(debugFP, "\n");
1813             }
1814             outcount = OutputToProcess(pr, buf, newcount, outError);
1815             if (outcount < newcount) return -1; /* to be sure */
1816             q = buf;
1817             newcount = 0;
1818         }
1819         if (*p == '\n') {
1820             *q++ = '\r';
1821             newcount++;
1822         } else if (((unsigned char) *p) == TN_IAC) {
1823             *q++ = (char) TN_IAC;
1824             newcount ++;
1825         }
1826         *q++ = *p++;
1827         newcount++;
1828         left--;
1829     }
1830     if (appData.debugMode) {
1831         fprintf(debugFP, ">ICS: ");
1832         show_bytes(debugFP, buf, newcount);
1833         fprintf(debugFP, "\n");
1834     }
1835     outcount = OutputToProcess(pr, buf, newcount, outError);
1836     if (outcount < newcount) return -1; /* to be sure */
1837     return count;
1838 }
1839
1840 void
1841 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1842 {
1843     int outError, outCount;
1844     static int gotEof = 0;
1845
1846     /* Pass data read from player on to ICS */
1847     if (count > 0) {
1848         gotEof = 0;
1849         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1850         if (outCount < count) {
1851             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1852         }
1853     } else if (count < 0) {
1854         RemoveInputSource(isr);
1855         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1856     } else if (gotEof++ > 0) {
1857         RemoveInputSource(isr);
1858         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1859     }
1860 }
1861
1862 void
1863 KeepAlive ()
1864 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1865     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1866     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1867     SendToICS("date\n");
1868     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1869 }
1870
1871 /* added routine for printf style output to ics */
1872 void
1873 ics_printf (char *format, ...)
1874 {
1875     char buffer[MSG_SIZ];
1876     va_list args;
1877
1878     va_start(args, format);
1879     vsnprintf(buffer, sizeof(buffer), format, args);
1880     buffer[sizeof(buffer)-1] = '\0';
1881     SendToICS(buffer);
1882     va_end(args);
1883 }
1884
1885 void
1886 SendToICS (char *s)
1887 {
1888     int count, outCount, outError;
1889
1890     if (icsPR == NoProc) return;
1891
1892     count = strlen(s);
1893     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1894     if (outCount < count) {
1895         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1896     }
1897 }
1898
1899 /* This is used for sending logon scripts to the ICS. Sending
1900    without a delay causes problems when using timestamp on ICC
1901    (at least on my machine). */
1902 void
1903 SendToICSDelayed (char *s, long msdelay)
1904 {
1905     int count, outCount, outError;
1906
1907     if (icsPR == NoProc) return;
1908
1909     count = strlen(s);
1910     if (appData.debugMode) {
1911         fprintf(debugFP, ">ICS: ");
1912         show_bytes(debugFP, s, count);
1913         fprintf(debugFP, "\n");
1914     }
1915     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1916                                       msdelay);
1917     if (outCount < count) {
1918         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1919     }
1920 }
1921
1922
1923 /* Remove all highlighting escape sequences in s
1924    Also deletes any suffix starting with '('
1925    */
1926 char *
1927 StripHighlightAndTitle (char *s)
1928 {
1929     static char retbuf[MSG_SIZ];
1930     char *p = retbuf;
1931
1932     while (*s != NULLCHAR) {
1933         while (*s == '\033') {
1934             while (*s != NULLCHAR && !isalpha(*s)) s++;
1935             if (*s != NULLCHAR) s++;
1936         }
1937         while (*s != NULLCHAR && *s != '\033') {
1938             if (*s == '(' || *s == '[') {
1939                 *p = NULLCHAR;
1940                 return retbuf;
1941             }
1942             *p++ = *s++;
1943         }
1944     }
1945     *p = NULLCHAR;
1946     return retbuf;
1947 }
1948
1949 /* Remove all highlighting escape sequences in s */
1950 char *
1951 StripHighlight (char *s)
1952 {
1953     static char retbuf[MSG_SIZ];
1954     char *p = retbuf;
1955
1956     while (*s != NULLCHAR) {
1957         while (*s == '\033') {
1958             while (*s != NULLCHAR && !isalpha(*s)) s++;
1959             if (*s != NULLCHAR) s++;
1960         }
1961         while (*s != NULLCHAR && *s != '\033') {
1962             *p++ = *s++;
1963         }
1964     }
1965     *p = NULLCHAR;
1966     return retbuf;
1967 }
1968
1969 char *variantNames[] = VARIANT_NAMES;
1970 char *
1971 VariantName (VariantClass v)
1972 {
1973     return variantNames[v];
1974 }
1975
1976
1977 /* Identify a variant from the strings the chess servers use or the
1978    PGN Variant tag names we use. */
1979 VariantClass
1980 StringToVariant (char *e)
1981 {
1982     char *p;
1983     int wnum = -1;
1984     VariantClass v = VariantNormal;
1985     int i, found = FALSE;
1986     char buf[MSG_SIZ];
1987     int len;
1988
1989     if (!e) return v;
1990
1991     /* [HGM] skip over optional board-size prefixes */
1992     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1993         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1994         while( *e++ != '_');
1995     }
1996
1997     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1998         v = VariantNormal;
1999         found = TRUE;
2000     } else
2001     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2002       if (StrCaseStr(e, variantNames[i])) {
2003         v = (VariantClass) i;
2004         found = TRUE;
2005         break;
2006       }
2007     }
2008
2009     if (!found) {
2010       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2011           || StrCaseStr(e, "wild/fr")
2012           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2013         v = VariantFischeRandom;
2014       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2015                  (i = 1, p = StrCaseStr(e, "w"))) {
2016         p += i;
2017         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2018         if (isdigit(*p)) {
2019           wnum = atoi(p);
2020         } else {
2021           wnum = -1;
2022         }
2023         switch (wnum) {
2024         case 0: /* FICS only, actually */
2025         case 1:
2026           /* Castling legal even if K starts on d-file */
2027           v = VariantWildCastle;
2028           break;
2029         case 2:
2030         case 3:
2031         case 4:
2032           /* Castling illegal even if K & R happen to start in
2033              normal positions. */
2034           v = VariantNoCastle;
2035           break;
2036         case 5:
2037         case 7:
2038         case 8:
2039         case 10:
2040         case 11:
2041         case 12:
2042         case 13:
2043         case 14:
2044         case 15:
2045         case 18:
2046         case 19:
2047           /* Castling legal iff K & R start in normal positions */
2048           v = VariantNormal;
2049           break;
2050         case 6:
2051         case 20:
2052         case 21:
2053           /* Special wilds for position setup; unclear what to do here */
2054           v = VariantLoadable;
2055           break;
2056         case 9:
2057           /* Bizarre ICC game */
2058           v = VariantTwoKings;
2059           break;
2060         case 16:
2061           v = VariantKriegspiel;
2062           break;
2063         case 17:
2064           v = VariantLosers;
2065           break;
2066         case 22:
2067           v = VariantFischeRandom;
2068           break;
2069         case 23:
2070           v = VariantCrazyhouse;
2071           break;
2072         case 24:
2073           v = VariantBughouse;
2074           break;
2075         case 25:
2076           v = Variant3Check;
2077           break;
2078         case 26:
2079           /* Not quite the same as FICS suicide! */
2080           v = VariantGiveaway;
2081           break;
2082         case 27:
2083           v = VariantAtomic;
2084           break;
2085         case 28:
2086           v = VariantShatranj;
2087           break;
2088
2089         /* Temporary names for future ICC types.  The name *will* change in
2090            the next xboard/WinBoard release after ICC defines it. */
2091         case 29:
2092           v = Variant29;
2093           break;
2094         case 30:
2095           v = Variant30;
2096           break;
2097         case 31:
2098           v = Variant31;
2099           break;
2100         case 32:
2101           v = Variant32;
2102           break;
2103         case 33:
2104           v = Variant33;
2105           break;
2106         case 34:
2107           v = Variant34;
2108           break;
2109         case 35:
2110           v = Variant35;
2111           break;
2112         case 36:
2113           v = Variant36;
2114           break;
2115         case 37:
2116           v = VariantShogi;
2117           break;
2118         case 38:
2119           v = VariantXiangqi;
2120           break;
2121         case 39:
2122           v = VariantCourier;
2123           break;
2124         case 40:
2125           v = VariantGothic;
2126           break;
2127         case 41:
2128           v = VariantCapablanca;
2129           break;
2130         case 42:
2131           v = VariantKnightmate;
2132           break;
2133         case 43:
2134           v = VariantFairy;
2135           break;
2136         case 44:
2137           v = VariantCylinder;
2138           break;
2139         case 45:
2140           v = VariantFalcon;
2141           break;
2142         case 46:
2143           v = VariantCapaRandom;
2144           break;
2145         case 47:
2146           v = VariantBerolina;
2147           break;
2148         case 48:
2149           v = VariantJanus;
2150           break;
2151         case 49:
2152           v = VariantSuper;
2153           break;
2154         case 50:
2155           v = VariantGreat;
2156           break;
2157         case -1:
2158           /* Found "wild" or "w" in the string but no number;
2159              must assume it's normal chess. */
2160           v = VariantNormal;
2161           break;
2162         default:
2163           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2164           if( (len >= MSG_SIZ) && appData.debugMode )
2165             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2166
2167           DisplayError(buf, 0);
2168           v = VariantUnknown;
2169           break;
2170         }
2171       }
2172     }
2173     if (appData.debugMode) {
2174       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2175               e, wnum, VariantName(v));
2176     }
2177     return v;
2178 }
2179
2180 static int leftover_start = 0, leftover_len = 0;
2181 char star_match[STAR_MATCH_N][MSG_SIZ];
2182
2183 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2184    advance *index beyond it, and set leftover_start to the new value of
2185    *index; else return FALSE.  If pattern contains the character '*', it
2186    matches any sequence of characters not containing '\r', '\n', or the
2187    character following the '*' (if any), and the matched sequence(s) are
2188    copied into star_match.
2189    */
2190 int
2191 looking_at ( char *buf, int *index, char *pattern)
2192 {
2193     char *bufp = &buf[*index], *patternp = pattern;
2194     int star_count = 0;
2195     char *matchp = star_match[0];
2196
2197     for (;;) {
2198         if (*patternp == NULLCHAR) {
2199             *index = leftover_start = bufp - buf;
2200             *matchp = NULLCHAR;
2201             return TRUE;
2202         }
2203         if (*bufp == NULLCHAR) return FALSE;
2204         if (*patternp == '*') {
2205             if (*bufp == *(patternp + 1)) {
2206                 *matchp = NULLCHAR;
2207                 matchp = star_match[++star_count];
2208                 patternp += 2;
2209                 bufp++;
2210                 continue;
2211             } else if (*bufp == '\n' || *bufp == '\r') {
2212                 patternp++;
2213                 if (*patternp == NULLCHAR)
2214                   continue;
2215                 else
2216                   return FALSE;
2217             } else {
2218                 *matchp++ = *bufp++;
2219                 continue;
2220             }
2221         }
2222         if (*patternp != *bufp) return FALSE;
2223         patternp++;
2224         bufp++;
2225     }
2226 }
2227
2228 void
2229 SendToPlayer (char *data, int length)
2230 {
2231     int error, outCount;
2232     outCount = OutputToProcess(NoProc, data, length, &error);
2233     if (outCount < length) {
2234         DisplayFatalError(_("Error writing to display"), error, 1);
2235     }
2236 }
2237
2238 void
2239 PackHolding (char packed[], char *holding)
2240 {
2241     char *p = holding;
2242     char *q = packed;
2243     int runlength = 0;
2244     int curr = 9999;
2245     do {
2246         if (*p == curr) {
2247             runlength++;
2248         } else {
2249             switch (runlength) {
2250               case 0:
2251                 break;
2252               case 1:
2253                 *q++ = curr;
2254                 break;
2255               case 2:
2256                 *q++ = curr;
2257                 *q++ = curr;
2258                 break;
2259               default:
2260                 sprintf(q, "%d", runlength);
2261                 while (*q) q++;
2262                 *q++ = curr;
2263                 break;
2264             }
2265             runlength = 1;
2266             curr = *p;
2267         }
2268     } while (*p++);
2269     *q = NULLCHAR;
2270 }
2271
2272 /* Telnet protocol requests from the front end */
2273 void
2274 TelnetRequest (unsigned char ddww, unsigned char option)
2275 {
2276     unsigned char msg[3];
2277     int outCount, outError;
2278
2279     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2280
2281     if (appData.debugMode) {
2282         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2283         switch (ddww) {
2284           case TN_DO:
2285             ddwwStr = "DO";
2286             break;
2287           case TN_DONT:
2288             ddwwStr = "DONT";
2289             break;
2290           case TN_WILL:
2291             ddwwStr = "WILL";
2292             break;
2293           case TN_WONT:
2294             ddwwStr = "WONT";
2295             break;
2296           default:
2297             ddwwStr = buf1;
2298             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2299             break;
2300         }
2301         switch (option) {
2302           case TN_ECHO:
2303             optionStr = "ECHO";
2304             break;
2305           default:
2306             optionStr = buf2;
2307             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2308             break;
2309         }
2310         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2311     }
2312     msg[0] = TN_IAC;
2313     msg[1] = ddww;
2314     msg[2] = option;
2315     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2316     if (outCount < 3) {
2317         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2318     }
2319 }
2320
2321 void
2322 DoEcho ()
2323 {
2324     if (!appData.icsActive) return;
2325     TelnetRequest(TN_DO, TN_ECHO);
2326 }
2327
2328 void
2329 DontEcho ()
2330 {
2331     if (!appData.icsActive) return;
2332     TelnetRequest(TN_DONT, TN_ECHO);
2333 }
2334
2335 void
2336 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2337 {
2338     /* put the holdings sent to us by the server on the board holdings area */
2339     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2340     char p;
2341     ChessSquare piece;
2342
2343     if(gameInfo.holdingsWidth < 2)  return;
2344     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2345         return; // prevent overwriting by pre-board holdings
2346
2347     if( (int)lowestPiece >= BlackPawn ) {
2348         holdingsColumn = 0;
2349         countsColumn = 1;
2350         holdingsStartRow = BOARD_HEIGHT-1;
2351         direction = -1;
2352     } else {
2353         holdingsColumn = BOARD_WIDTH-1;
2354         countsColumn = BOARD_WIDTH-2;
2355         holdingsStartRow = 0;
2356         direction = 1;
2357     }
2358
2359     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2360         board[i][holdingsColumn] = EmptySquare;
2361         board[i][countsColumn]   = (ChessSquare) 0;
2362     }
2363     while( (p=*holdings++) != NULLCHAR ) {
2364         piece = CharToPiece( ToUpper(p) );
2365         if(piece == EmptySquare) continue;
2366         /*j = (int) piece - (int) WhitePawn;*/
2367         j = PieceToNumber(piece);
2368         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2369         if(j < 0) continue;               /* should not happen */
2370         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2371         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2372         board[holdingsStartRow+j*direction][countsColumn]++;
2373     }
2374 }
2375
2376
2377 void
2378 VariantSwitch (Board board, VariantClass newVariant)
2379 {
2380    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2381    static Board oldBoard;
2382
2383    startedFromPositionFile = FALSE;
2384    if(gameInfo.variant == newVariant) return;
2385
2386    /* [HGM] This routine is called each time an assignment is made to
2387     * gameInfo.variant during a game, to make sure the board sizes
2388     * are set to match the new variant. If that means adding or deleting
2389     * holdings, we shift the playing board accordingly
2390     * This kludge is needed because in ICS observe mode, we get boards
2391     * of an ongoing game without knowing the variant, and learn about the
2392     * latter only later. This can be because of the move list we requested,
2393     * in which case the game history is refilled from the beginning anyway,
2394     * but also when receiving holdings of a crazyhouse game. In the latter
2395     * case we want to add those holdings to the already received position.
2396     */
2397
2398
2399    if (appData.debugMode) {
2400      fprintf(debugFP, "Switch board from %s to %s\n",
2401              VariantName(gameInfo.variant), VariantName(newVariant));
2402      setbuf(debugFP, NULL);
2403    }
2404    shuffleOpenings = 0;       /* [HGM] shuffle */
2405    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2406    switch(newVariant)
2407      {
2408      case VariantShogi:
2409        newWidth = 9;  newHeight = 9;
2410        gameInfo.holdingsSize = 7;
2411      case VariantBughouse:
2412      case VariantCrazyhouse:
2413        newHoldingsWidth = 2; break;
2414      case VariantGreat:
2415        newWidth = 10;
2416      case VariantSuper:
2417        newHoldingsWidth = 2;
2418        gameInfo.holdingsSize = 8;
2419        break;
2420      case VariantGothic:
2421      case VariantCapablanca:
2422      case VariantCapaRandom:
2423        newWidth = 10;
2424      default:
2425        newHoldingsWidth = gameInfo.holdingsSize = 0;
2426      };
2427
2428    if(newWidth  != gameInfo.boardWidth  ||
2429       newHeight != gameInfo.boardHeight ||
2430       newHoldingsWidth != gameInfo.holdingsWidth ) {
2431
2432      /* shift position to new playing area, if needed */
2433      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2434        for(i=0; i<BOARD_HEIGHT; i++)
2435          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2436            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2437              board[i][j];
2438        for(i=0; i<newHeight; i++) {
2439          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2440          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2441        }
2442      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2443        for(i=0; i<BOARD_HEIGHT; i++)
2444          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2445            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2446              board[i][j];
2447      }
2448      board[HOLDINGS_SET] = 0;
2449      gameInfo.boardWidth  = newWidth;
2450      gameInfo.boardHeight = newHeight;
2451      gameInfo.holdingsWidth = newHoldingsWidth;
2452      gameInfo.variant = newVariant;
2453      InitDrawingSizes(-2, 0);
2454    } else gameInfo.variant = newVariant;
2455    CopyBoard(oldBoard, board);   // remember correctly formatted board
2456      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2457    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2458 }
2459
2460 static int loggedOn = FALSE;
2461
2462 /*-- Game start info cache: --*/
2463 int gs_gamenum;
2464 char gs_kind[MSG_SIZ];
2465 static char player1Name[128] = "";
2466 static char player2Name[128] = "";
2467 static char cont_seq[] = "\n\\   ";
2468 static int player1Rating = -1;
2469 static int player2Rating = -1;
2470 /*----------------------------*/
2471
2472 ColorClass curColor = ColorNormal;
2473 int suppressKibitz = 0;
2474
2475 // [HGM] seekgraph
2476 Boolean soughtPending = FALSE;
2477 Boolean seekGraphUp;
2478 #define MAX_SEEK_ADS 200
2479 #define SQUARE 0x80
2480 char *seekAdList[MAX_SEEK_ADS];
2481 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2482 float tcList[MAX_SEEK_ADS];
2483 char colorList[MAX_SEEK_ADS];
2484 int nrOfSeekAds = 0;
2485 int minRating = 1010, maxRating = 2800;
2486 int hMargin = 10, vMargin = 20, h, w;
2487 extern int squareSize, lineGap;
2488
2489 void
2490 PlotSeekAd (int i)
2491 {
2492         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2493         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2494         if(r < minRating+100 && r >=0 ) r = minRating+100;
2495         if(r > maxRating) r = maxRating;
2496         if(tc < 1.f) tc = 1.f;
2497         if(tc > 95.f) tc = 95.f;
2498         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2499         y = ((double)r - minRating)/(maxRating - minRating)
2500             * (h-vMargin-squareSize/8-1) + vMargin;
2501         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2502         if(strstr(seekAdList[i], " u ")) color = 1;
2503         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2504            !strstr(seekAdList[i], "bullet") &&
2505            !strstr(seekAdList[i], "blitz") &&
2506            !strstr(seekAdList[i], "standard") ) color = 2;
2507         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2508         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2509 }
2510
2511 void
2512 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2513 {
2514         char buf[MSG_SIZ], *ext = "";
2515         VariantClass v = StringToVariant(type);
2516         if(strstr(type, "wild")) {
2517             ext = type + 4; // append wild number
2518             if(v == VariantFischeRandom) type = "chess960"; else
2519             if(v == VariantLoadable) type = "setup"; else
2520             type = VariantName(v);
2521         }
2522         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2523         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2524             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2525             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2526             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2527             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2528             seekNrList[nrOfSeekAds] = nr;
2529             zList[nrOfSeekAds] = 0;
2530             seekAdList[nrOfSeekAds++] = StrSave(buf);
2531             if(plot) PlotSeekAd(nrOfSeekAds-1);
2532         }
2533 }
2534
2535 void
2536 EraseSeekDot (int i)
2537 {
2538     int x = xList[i], y = yList[i], d=squareSize/4, k;
2539     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2540     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2541     // now replot every dot that overlapped
2542     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2543         int xx = xList[k], yy = yList[k];
2544         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2545             DrawSeekDot(xx, yy, colorList[k]);
2546     }
2547 }
2548
2549 void
2550 RemoveSeekAd (int nr)
2551 {
2552         int i;
2553         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2554             EraseSeekDot(i);
2555             if(seekAdList[i]) free(seekAdList[i]);
2556             seekAdList[i] = seekAdList[--nrOfSeekAds];
2557             seekNrList[i] = seekNrList[nrOfSeekAds];
2558             ratingList[i] = ratingList[nrOfSeekAds];
2559             colorList[i]  = colorList[nrOfSeekAds];
2560             tcList[i] = tcList[nrOfSeekAds];
2561             xList[i]  = xList[nrOfSeekAds];
2562             yList[i]  = yList[nrOfSeekAds];
2563             zList[i]  = zList[nrOfSeekAds];
2564             seekAdList[nrOfSeekAds] = NULL;
2565             break;
2566         }
2567 }
2568
2569 Boolean
2570 MatchSoughtLine (char *line)
2571 {
2572     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2573     int nr, base, inc, u=0; char dummy;
2574
2575     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2576        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2577        (u=1) &&
2578        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2579         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2580         // match: compact and save the line
2581         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2582         return TRUE;
2583     }
2584     return FALSE;
2585 }
2586
2587 int
2588 DrawSeekGraph ()
2589 {
2590     int i;
2591     if(!seekGraphUp) return FALSE;
2592     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2593     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2594
2595     DrawSeekBackground(0, 0, w, h);
2596     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2597     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2598     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2599         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2600         yy = h-1-yy;
2601         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2602         if(i%500 == 0) {
2603             char buf[MSG_SIZ];
2604             snprintf(buf, MSG_SIZ, "%d", i);
2605             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2606         }
2607     }
2608     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2609     for(i=1; i<100; i+=(i<10?1:5)) {
2610         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2611         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2612         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2613             char buf[MSG_SIZ];
2614             snprintf(buf, MSG_SIZ, "%d", i);
2615             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2616         }
2617     }
2618     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2619     return TRUE;
2620 }
2621
2622 int
2623 SeekGraphClick (ClickType click, int x, int y, int moving)
2624 {
2625     static int lastDown = 0, displayed = 0, lastSecond;
2626     if(y < 0) return FALSE;
2627     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2628         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2629         if(!seekGraphUp) return FALSE;
2630         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2631         DrawPosition(TRUE, NULL);
2632         return TRUE;
2633     }
2634     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2635         if(click == Release || moving) return FALSE;
2636         nrOfSeekAds = 0;
2637         soughtPending = TRUE;
2638         SendToICS(ics_prefix);
2639         SendToICS("sought\n"); // should this be "sought all"?
2640     } else { // issue challenge based on clicked ad
2641         int dist = 10000; int i, closest = 0, second = 0;
2642         for(i=0; i<nrOfSeekAds; i++) {
2643             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2644             if(d < dist) { dist = d; closest = i; }
2645             second += (d - zList[i] < 120); // count in-range ads
2646             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2647         }
2648         if(dist < 120) {
2649             char buf[MSG_SIZ];
2650             second = (second > 1);
2651             if(displayed != closest || second != lastSecond) {
2652                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2653                 lastSecond = second; displayed = closest;
2654             }
2655             if(click == Press) {
2656                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2657                 lastDown = closest;
2658                 return TRUE;
2659             } // on press 'hit', only show info
2660             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2661             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2662             SendToICS(ics_prefix);
2663             SendToICS(buf);
2664             return TRUE; // let incoming board of started game pop down the graph
2665         } else if(click == Release) { // release 'miss' is ignored
2666             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2667             if(moving == 2) { // right up-click
2668                 nrOfSeekAds = 0; // refresh graph
2669                 soughtPending = TRUE;
2670                 SendToICS(ics_prefix);
2671                 SendToICS("sought\n"); // should this be "sought all"?
2672             }
2673             return TRUE;
2674         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2675         // press miss or release hit 'pop down' seek graph
2676         seekGraphUp = FALSE;
2677         DrawPosition(TRUE, NULL);
2678     }
2679     return TRUE;
2680 }
2681
2682 void
2683 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2684 {
2685 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2686 #define STARTED_NONE 0
2687 #define STARTED_MOVES 1
2688 #define STARTED_BOARD 2
2689 #define STARTED_OBSERVE 3
2690 #define STARTED_HOLDINGS 4
2691 #define STARTED_CHATTER 5
2692 #define STARTED_COMMENT 6
2693 #define STARTED_MOVES_NOHIDE 7
2694
2695     static int started = STARTED_NONE;
2696     static char parse[20000];
2697     static int parse_pos = 0;
2698     static char buf[BUF_SIZE + 1];
2699     static int firstTime = TRUE, intfSet = FALSE;
2700     static ColorClass prevColor = ColorNormal;
2701     static int savingComment = FALSE;
2702     static int cmatch = 0; // continuation sequence match
2703     char *bp;
2704     char str[MSG_SIZ];
2705     int i, oldi;
2706     int buf_len;
2707     int next_out;
2708     int tkind;
2709     int backup;    /* [DM] For zippy color lines */
2710     char *p;
2711     char talker[MSG_SIZ]; // [HGM] chat
2712     int channel;
2713
2714     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2715
2716     if (appData.debugMode) {
2717       if (!error) {
2718         fprintf(debugFP, "<ICS: ");
2719         show_bytes(debugFP, data, count);
2720         fprintf(debugFP, "\n");
2721       }
2722     }
2723
2724     if (appData.debugMode) { int f = forwardMostMove;
2725         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2726                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2727                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2728     }
2729     if (count > 0) {
2730         /* If last read ended with a partial line that we couldn't parse,
2731            prepend it to the new read and try again. */
2732         if (leftover_len > 0) {
2733             for (i=0; i<leftover_len; i++)
2734               buf[i] = buf[leftover_start + i];
2735         }
2736
2737     /* copy new characters into the buffer */
2738     bp = buf + leftover_len;
2739     buf_len=leftover_len;
2740     for (i=0; i<count; i++)
2741     {
2742         // ignore these
2743         if (data[i] == '\r')
2744             continue;
2745
2746         // join lines split by ICS?
2747         if (!appData.noJoin)
2748         {
2749             /*
2750                 Joining just consists of finding matches against the
2751                 continuation sequence, and discarding that sequence
2752                 if found instead of copying it.  So, until a match
2753                 fails, there's nothing to do since it might be the
2754                 complete sequence, and thus, something we don't want
2755                 copied.
2756             */
2757             if (data[i] == cont_seq[cmatch])
2758             {
2759                 cmatch++;
2760                 if (cmatch == strlen(cont_seq))
2761                 {
2762                     cmatch = 0; // complete match.  just reset the counter
2763
2764                     /*
2765                         it's possible for the ICS to not include the space
2766                         at the end of the last word, making our [correct]
2767                         join operation fuse two separate words.  the server
2768                         does this when the space occurs at the width setting.
2769                     */
2770                     if (!buf_len || buf[buf_len-1] != ' ')
2771                     {
2772                         *bp++ = ' ';
2773                         buf_len++;
2774                     }
2775                 }
2776                 continue;
2777             }
2778             else if (cmatch)
2779             {
2780                 /*
2781                     match failed, so we have to copy what matched before
2782                     falling through and copying this character.  In reality,
2783                     this will only ever be just the newline character, but
2784                     it doesn't hurt to be precise.
2785                 */
2786                 strncpy(bp, cont_seq, cmatch);
2787                 bp += cmatch;
2788                 buf_len += cmatch;
2789                 cmatch = 0;
2790             }
2791         }
2792
2793         // copy this char
2794         *bp++ = data[i];
2795         buf_len++;
2796     }
2797
2798         buf[buf_len] = NULLCHAR;
2799 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2800         next_out = 0;
2801         leftover_start = 0;
2802
2803         i = 0;
2804         while (i < buf_len) {
2805             /* Deal with part of the TELNET option negotiation
2806                protocol.  We refuse to do anything beyond the
2807                defaults, except that we allow the WILL ECHO option,
2808                which ICS uses to turn off password echoing when we are
2809                directly connected to it.  We reject this option
2810                if localLineEditing mode is on (always on in xboard)
2811                and we are talking to port 23, which might be a real
2812                telnet server that will try to keep WILL ECHO on permanently.
2813              */
2814             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2815                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2816                 unsigned char option;
2817                 oldi = i;
2818                 switch ((unsigned char) buf[++i]) {
2819                   case TN_WILL:
2820                     if (appData.debugMode)
2821                       fprintf(debugFP, "\n<WILL ");
2822                     switch (option = (unsigned char) buf[++i]) {
2823                       case TN_ECHO:
2824                         if (appData.debugMode)
2825                           fprintf(debugFP, "ECHO ");
2826                         /* Reply only if this is a change, according
2827                            to the protocol rules. */
2828                         if (remoteEchoOption) break;
2829                         if (appData.localLineEditing &&
2830                             atoi(appData.icsPort) == TN_PORT) {
2831                             TelnetRequest(TN_DONT, TN_ECHO);
2832                         } else {
2833                             EchoOff();
2834                             TelnetRequest(TN_DO, TN_ECHO);
2835                             remoteEchoOption = TRUE;
2836                         }
2837                         break;
2838                       default:
2839                         if (appData.debugMode)
2840                           fprintf(debugFP, "%d ", option);
2841                         /* Whatever this is, we don't want it. */
2842                         TelnetRequest(TN_DONT, option);
2843                         break;
2844                     }
2845                     break;
2846                   case TN_WONT:
2847                     if (appData.debugMode)
2848                       fprintf(debugFP, "\n<WONT ");
2849                     switch (option = (unsigned char) buf[++i]) {
2850                       case TN_ECHO:
2851                         if (appData.debugMode)
2852                           fprintf(debugFP, "ECHO ");
2853                         /* Reply only if this is a change, according
2854                            to the protocol rules. */
2855                         if (!remoteEchoOption) break;
2856                         EchoOn();
2857                         TelnetRequest(TN_DONT, TN_ECHO);
2858                         remoteEchoOption = FALSE;
2859                         break;
2860                       default:
2861                         if (appData.debugMode)
2862                           fprintf(debugFP, "%d ", (unsigned char) option);
2863                         /* Whatever this is, it must already be turned
2864                            off, because we never agree to turn on
2865                            anything non-default, so according to the
2866                            protocol rules, we don't reply. */
2867                         break;
2868                     }
2869                     break;
2870                   case TN_DO:
2871                     if (appData.debugMode)
2872                       fprintf(debugFP, "\n<DO ");
2873                     switch (option = (unsigned char) buf[++i]) {
2874                       default:
2875                         /* Whatever this is, we refuse to do it. */
2876                         if (appData.debugMode)
2877                           fprintf(debugFP, "%d ", option);
2878                         TelnetRequest(TN_WONT, option);
2879                         break;
2880                     }
2881                     break;
2882                   case TN_DONT:
2883                     if (appData.debugMode)
2884                       fprintf(debugFP, "\n<DONT ");
2885                     switch (option = (unsigned char) buf[++i]) {
2886                       default:
2887                         if (appData.debugMode)
2888                           fprintf(debugFP, "%d ", option);
2889                         /* Whatever this is, we are already not doing
2890                            it, because we never agree to do anything
2891                            non-default, so according to the protocol
2892                            rules, we don't reply. */
2893                         break;
2894                     }
2895                     break;
2896                   case TN_IAC:
2897                     if (appData.debugMode)
2898                       fprintf(debugFP, "\n<IAC ");
2899                     /* Doubled IAC; pass it through */
2900                     i--;
2901                     break;
2902                   default:
2903                     if (appData.debugMode)
2904                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2905                     /* Drop all other telnet commands on the floor */
2906                     break;
2907                 }
2908                 if (oldi > next_out)
2909                   SendToPlayer(&buf[next_out], oldi - next_out);
2910                 if (++i > next_out)
2911                   next_out = i;
2912                 continue;
2913             }
2914
2915             /* OK, this at least will *usually* work */
2916             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2917                 loggedOn = TRUE;
2918             }
2919
2920             if (loggedOn && !intfSet) {
2921                 if (ics_type == ICS_ICC) {
2922                   snprintf(str, MSG_SIZ,
2923                           "/set-quietly interface %s\n/set-quietly style 12\n",
2924                           programVersion);
2925                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2926                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2927                 } else if (ics_type == ICS_CHESSNET) {
2928                   snprintf(str, MSG_SIZ, "/style 12\n");
2929                 } else {
2930                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2931                   strcat(str, programVersion);
2932                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2933                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2934                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2935 #ifdef WIN32
2936                   strcat(str, "$iset nohighlight 1\n");
2937 #endif
2938                   strcat(str, "$iset lock 1\n$style 12\n");
2939                 }
2940                 SendToICS(str);
2941                 NotifyFrontendLogin();
2942                 intfSet = TRUE;
2943             }
2944
2945             if (started == STARTED_COMMENT) {
2946                 /* Accumulate characters in comment */
2947                 parse[parse_pos++] = buf[i];
2948                 if (buf[i] == '\n') {
2949                     parse[parse_pos] = NULLCHAR;
2950                     if(chattingPartner>=0) {
2951                         char mess[MSG_SIZ];
2952                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2953                         OutputChatMessage(chattingPartner, mess);
2954                         chattingPartner = -1;
2955                         next_out = i+1; // [HGM] suppress printing in ICS window
2956                     } else
2957                     if(!suppressKibitz) // [HGM] kibitz
2958                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2959                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2960                         int nrDigit = 0, nrAlph = 0, j;
2961                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2962                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2963                         parse[parse_pos] = NULLCHAR;
2964                         // try to be smart: if it does not look like search info, it should go to
2965                         // ICS interaction window after all, not to engine-output window.
2966                         for(j=0; j<parse_pos; j++) { // count letters and digits
2967                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2968                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2969                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2970                         }
2971                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2972                             int depth=0; float score;
2973                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2974                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2975                                 pvInfoList[forwardMostMove-1].depth = depth;
2976                                 pvInfoList[forwardMostMove-1].score = 100*score;
2977                             }
2978                             OutputKibitz(suppressKibitz, parse);
2979                         } else {
2980                             char tmp[MSG_SIZ];
2981                             if(gameMode == IcsObserving) // restore original ICS messages
2982                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2983                             else
2984                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2985                             SendToPlayer(tmp, strlen(tmp));
2986                         }
2987                         next_out = i+1; // [HGM] suppress printing in ICS window
2988                     }
2989                     started = STARTED_NONE;
2990                 } else {
2991                     /* Don't match patterns against characters in comment */
2992                     i++;
2993                     continue;
2994                 }
2995             }
2996             if (started == STARTED_CHATTER) {
2997                 if (buf[i] != '\n') {
2998                     /* Don't match patterns against characters in chatter */
2999                     i++;
3000                     continue;
3001                 }
3002                 started = STARTED_NONE;
3003                 if(suppressKibitz) next_out = i+1;
3004             }
3005
3006             /* Kludge to deal with rcmd protocol */
3007             if (firstTime && looking_at(buf, &i, "\001*")) {
3008                 DisplayFatalError(&buf[1], 0, 1);
3009                 continue;
3010             } else {
3011                 firstTime = FALSE;
3012             }
3013
3014             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3015                 ics_type = ICS_ICC;
3016                 ics_prefix = "/";
3017                 if (appData.debugMode)
3018                   fprintf(debugFP, "ics_type %d\n", ics_type);
3019                 continue;
3020             }
3021             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3022                 ics_type = ICS_FICS;
3023                 ics_prefix = "$";
3024                 if (appData.debugMode)
3025                   fprintf(debugFP, "ics_type %d\n", ics_type);
3026                 continue;
3027             }
3028             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3029                 ics_type = ICS_CHESSNET;
3030                 ics_prefix = "/";
3031                 if (appData.debugMode)
3032                   fprintf(debugFP, "ics_type %d\n", ics_type);
3033                 continue;
3034             }
3035
3036             if (!loggedOn &&
3037                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3038                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3039                  looking_at(buf, &i, "will be \"*\""))) {
3040               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3041               continue;
3042             }
3043
3044             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3045               char buf[MSG_SIZ];
3046               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3047               DisplayIcsInteractionTitle(buf);
3048               have_set_title = TRUE;
3049             }
3050
3051             /* skip finger notes */
3052             if (started == STARTED_NONE &&
3053                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3054                  (buf[i] == '1' && buf[i+1] == '0')) &&
3055                 buf[i+2] == ':' && buf[i+3] == ' ') {
3056               started = STARTED_CHATTER;
3057               i += 3;
3058               continue;
3059             }
3060
3061             oldi = i;
3062             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3063             if(appData.seekGraph) {
3064                 if(soughtPending && MatchSoughtLine(buf+i)) {
3065                     i = strstr(buf+i, "rated") - buf;
3066                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3067                     next_out = leftover_start = i;
3068                     started = STARTED_CHATTER;
3069                     suppressKibitz = TRUE;
3070                     continue;
3071                 }
3072                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3073                         && looking_at(buf, &i, "* ads displayed")) {
3074                     soughtPending = FALSE;
3075                     seekGraphUp = TRUE;
3076                     DrawSeekGraph();
3077                     continue;
3078                 }
3079                 if(appData.autoRefresh) {
3080                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3081                         int s = (ics_type == ICS_ICC); // ICC format differs
3082                         if(seekGraphUp)
3083                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3084                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3085                         looking_at(buf, &i, "*% "); // eat prompt
3086                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3087                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3088                         next_out = i; // suppress
3089                         continue;
3090                     }
3091                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3092                         char *p = star_match[0];
3093                         while(*p) {
3094                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3095                             while(*p && *p++ != ' '); // next
3096                         }
3097                         looking_at(buf, &i, "*% "); // eat prompt
3098                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3099                         next_out = i;
3100                         continue;
3101                     }
3102                 }
3103             }
3104
3105             /* skip formula vars */
3106             if (started == STARTED_NONE &&
3107                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3108               started = STARTED_CHATTER;
3109               i += 3;
3110               continue;
3111             }
3112
3113             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3114             if (appData.autoKibitz && started == STARTED_NONE &&
3115                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3116                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3117                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3118                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3119                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3120                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3121                         suppressKibitz = TRUE;
3122                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3123                         next_out = i;
3124                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3125                                 && (gameMode == IcsPlayingWhite)) ||
3126                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3127                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3128                             started = STARTED_CHATTER; // own kibitz we simply discard
3129                         else {
3130                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3131                             parse_pos = 0; parse[0] = NULLCHAR;
3132                             savingComment = TRUE;
3133                             suppressKibitz = gameMode != IcsObserving ? 2 :
3134                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3135                         }
3136                         continue;
3137                 } else
3138                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3139                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3140                          && atoi(star_match[0])) {
3141                     // suppress the acknowledgements of our own autoKibitz
3142                     char *p;
3143                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3144                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3145                     SendToPlayer(star_match[0], strlen(star_match[0]));
3146                     if(looking_at(buf, &i, "*% ")) // eat prompt
3147                         suppressKibitz = FALSE;
3148                     next_out = i;
3149                     continue;
3150                 }
3151             } // [HGM] kibitz: end of patch
3152
3153             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3154
3155             // [HGM] chat: intercept tells by users for which we have an open chat window
3156             channel = -1;
3157             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3158                                            looking_at(buf, &i, "* whispers:") ||
3159                                            looking_at(buf, &i, "* kibitzes:") ||
3160                                            looking_at(buf, &i, "* shouts:") ||
3161                                            looking_at(buf, &i, "* c-shouts:") ||
3162                                            looking_at(buf, &i, "--> * ") ||
3163                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3164                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3165                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3166                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3167                 int p;
3168                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3169                 chattingPartner = -1;
3170
3171                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3172                 for(p=0; p<MAX_CHAT; p++) {
3173                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3174                     talker[0] = '['; strcat(talker, "] ");
3175                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3176                     chattingPartner = p; break;
3177                     }
3178                 } else
3179                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3180                 for(p=0; p<MAX_CHAT; p++) {
3181                     if(!strcmp("kibitzes", chatPartner[p])) {
3182                         talker[0] = '['; strcat(talker, "] ");
3183                         chattingPartner = p; break;
3184                     }
3185                 } else
3186                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3187                 for(p=0; p<MAX_CHAT; p++) {
3188                     if(!strcmp("whispers", chatPartner[p])) {
3189                         talker[0] = '['; strcat(talker, "] ");
3190                         chattingPartner = p; break;
3191                     }
3192                 } else
3193                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3194                   if(buf[i-8] == '-' && buf[i-3] == 't')
3195                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3196                     if(!strcmp("c-shouts", chatPartner[p])) {
3197                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3198                         chattingPartner = p; break;
3199                     }
3200                   }
3201                   if(chattingPartner < 0)
3202                   for(p=0; p<MAX_CHAT; p++) {
3203                     if(!strcmp("shouts", chatPartner[p])) {
3204                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3205                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3206                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3207                         chattingPartner = p; break;
3208                     }
3209                   }
3210                 }
3211                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3212                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3213                     talker[0] = 0; Colorize(ColorTell, FALSE);
3214                     chattingPartner = p; break;
3215                 }
3216                 if(chattingPartner<0) i = oldi; else {
3217                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3218                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3219                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3220                     started = STARTED_COMMENT;
3221                     parse_pos = 0; parse[0] = NULLCHAR;
3222                     savingComment = 3 + chattingPartner; // counts as TRUE
3223                     suppressKibitz = TRUE;
3224                     continue;
3225                 }
3226             } // [HGM] chat: end of patch
3227
3228           backup = i;
3229             if (appData.zippyTalk || appData.zippyPlay) {
3230                 /* [DM] Backup address for color zippy lines */
3231 #if ZIPPY
3232                if (loggedOn == TRUE)
3233                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3234                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3235 #endif
3236             } // [DM] 'else { ' deleted
3237                 if (
3238                     /* Regular tells and says */
3239                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3240                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3241                     looking_at(buf, &i, "* says: ") ||
3242                     /* Don't color "message" or "messages" output */
3243                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3244                     looking_at(buf, &i, "*. * at *:*: ") ||
3245                     looking_at(buf, &i, "--* (*:*): ") ||
3246                     /* Message notifications (same color as tells) */
3247                     looking_at(buf, &i, "* has left a message ") ||
3248                     looking_at(buf, &i, "* just sent you a message:\n") ||
3249                     /* Whispers and kibitzes */
3250                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3251                     looking_at(buf, &i, "* kibitzes: ") ||
3252                     /* Channel tells */
3253                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3254
3255                   if (tkind == 1 && strchr(star_match[0], ':')) {
3256                       /* Avoid "tells you:" spoofs in channels */
3257                      tkind = 3;
3258                   }
3259                   if (star_match[0][0] == NULLCHAR ||
3260                       strchr(star_match[0], ' ') ||
3261                       (tkind == 3 && strchr(star_match[1], ' '))) {
3262                     /* Reject bogus matches */
3263                     i = oldi;
3264                   } else {
3265                     if (appData.colorize) {
3266                       if (oldi > next_out) {
3267                         SendToPlayer(&buf[next_out], oldi - next_out);
3268                         next_out = oldi;
3269                       }
3270                       switch (tkind) {
3271                       case 1:
3272                         Colorize(ColorTell, FALSE);
3273                         curColor = ColorTell;
3274                         break;
3275                       case 2:
3276                         Colorize(ColorKibitz, FALSE);
3277                         curColor = ColorKibitz;
3278                         break;
3279                       case 3:
3280                         p = strrchr(star_match[1], '(');
3281                         if (p == NULL) {
3282                           p = star_match[1];
3283                         } else {
3284                           p++;
3285                         }
3286                         if (atoi(p) == 1) {
3287                           Colorize(ColorChannel1, FALSE);
3288                           curColor = ColorChannel1;
3289                         } else {
3290                           Colorize(ColorChannel, FALSE);
3291                           curColor = ColorChannel;
3292                         }
3293                         break;
3294                       case 5:
3295                         curColor = ColorNormal;
3296                         break;
3297                       }
3298                     }
3299                     if (started == STARTED_NONE && appData.autoComment &&
3300                         (gameMode == IcsObserving ||
3301                          gameMode == IcsPlayingWhite ||
3302                          gameMode == IcsPlayingBlack)) {
3303                       parse_pos = i - oldi;
3304                       memcpy(parse, &buf[oldi], parse_pos);
3305                       parse[parse_pos] = NULLCHAR;
3306                       started = STARTED_COMMENT;
3307                       savingComment = TRUE;
3308                     } else {
3309                       started = STARTED_CHATTER;
3310                       savingComment = FALSE;
3311                     }
3312                     loggedOn = TRUE;
3313                     continue;
3314                   }
3315                 }
3316
3317                 if (looking_at(buf, &i, "* s-shouts: ") ||
3318                     looking_at(buf, &i, "* c-shouts: ")) {
3319                     if (appData.colorize) {
3320                         if (oldi > next_out) {
3321                             SendToPlayer(&buf[next_out], oldi - next_out);
3322                             next_out = oldi;
3323                         }
3324                         Colorize(ColorSShout, FALSE);
3325                         curColor = ColorSShout;
3326                     }
3327                     loggedOn = TRUE;
3328                     started = STARTED_CHATTER;
3329                     continue;
3330                 }
3331
3332                 if (looking_at(buf, &i, "--->")) {
3333                     loggedOn = TRUE;
3334                     continue;
3335                 }
3336
3337                 if (looking_at(buf, &i, "* shouts: ") ||
3338                     looking_at(buf, &i, "--> ")) {
3339                     if (appData.colorize) {
3340                         if (oldi > next_out) {
3341                             SendToPlayer(&buf[next_out], oldi - next_out);
3342                             next_out = oldi;
3343                         }
3344                         Colorize(ColorShout, FALSE);
3345                         curColor = ColorShout;
3346                     }
3347                     loggedOn = TRUE;
3348                     started = STARTED_CHATTER;
3349                     continue;
3350                 }
3351
3352                 if (looking_at( buf, &i, "Challenge:")) {
3353                     if (appData.colorize) {
3354                         if (oldi > next_out) {
3355                             SendToPlayer(&buf[next_out], oldi - next_out);
3356                             next_out = oldi;
3357                         }
3358                         Colorize(ColorChallenge, FALSE);
3359                         curColor = ColorChallenge;
3360                     }
3361                     loggedOn = TRUE;
3362                     continue;
3363                 }
3364
3365                 if (looking_at(buf, &i, "* offers you") ||
3366                     looking_at(buf, &i, "* offers to be") ||
3367                     looking_at(buf, &i, "* would like to") ||
3368                     looking_at(buf, &i, "* requests to") ||
3369                     looking_at(buf, &i, "Your opponent offers") ||
3370                     looking_at(buf, &i, "Your opponent requests")) {
3371
3372                     if (appData.colorize) {
3373                         if (oldi > next_out) {
3374                             SendToPlayer(&buf[next_out], oldi - next_out);
3375                             next_out = oldi;
3376                         }
3377                         Colorize(ColorRequest, FALSE);
3378                         curColor = ColorRequest;
3379                     }
3380                     continue;
3381                 }
3382
3383                 if (looking_at(buf, &i, "* (*) seeking")) {
3384                     if (appData.colorize) {
3385                         if (oldi > next_out) {
3386                             SendToPlayer(&buf[next_out], oldi - next_out);
3387                             next_out = oldi;
3388                         }
3389                         Colorize(ColorSeek, FALSE);
3390                         curColor = ColorSeek;
3391                     }
3392                     continue;
3393             }
3394
3395           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3396
3397             if (looking_at(buf, &i, "\\   ")) {
3398                 if (prevColor != ColorNormal) {
3399                     if (oldi > next_out) {
3400                         SendToPlayer(&buf[next_out], oldi - next_out);
3401                         next_out = oldi;
3402                     }
3403                     Colorize(prevColor, TRUE);
3404                     curColor = prevColor;
3405                 }
3406                 if (savingComment) {
3407                     parse_pos = i - oldi;
3408                     memcpy(parse, &buf[oldi], parse_pos);
3409                     parse[parse_pos] = NULLCHAR;
3410                     started = STARTED_COMMENT;
3411                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3412                         chattingPartner = savingComment - 3; // kludge to remember the box
3413                 } else {
3414                     started = STARTED_CHATTER;
3415                 }
3416                 continue;
3417             }
3418
3419             if (looking_at(buf, &i, "Black Strength :") ||
3420                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3421                 looking_at(buf, &i, "<10>") ||
3422                 looking_at(buf, &i, "#@#")) {
3423                 /* Wrong board style */
3424                 loggedOn = TRUE;
3425                 SendToICS(ics_prefix);
3426                 SendToICS("set style 12\n");
3427                 SendToICS(ics_prefix);
3428                 SendToICS("refresh\n");
3429                 continue;
3430             }
3431
3432             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3433                 ICSInitScript();
3434                 have_sent_ICS_logon = 1;
3435                 continue;
3436             }
3437
3438             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3439                 (looking_at(buf, &i, "\n<12> ") ||
3440                  looking_at(buf, &i, "<12> "))) {
3441                 loggedOn = TRUE;
3442                 if (oldi > next_out) {
3443                     SendToPlayer(&buf[next_out], oldi - next_out);
3444                 }
3445                 next_out = i;
3446                 started = STARTED_BOARD;
3447                 parse_pos = 0;
3448                 continue;
3449             }
3450
3451             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3452                 looking_at(buf, &i, "<b1> ")) {
3453                 if (oldi > next_out) {
3454                     SendToPlayer(&buf[next_out], oldi - next_out);
3455                 }
3456                 next_out = i;
3457                 started = STARTED_HOLDINGS;
3458                 parse_pos = 0;
3459                 continue;
3460             }
3461
3462             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3463                 loggedOn = TRUE;
3464                 /* Header for a move list -- first line */
3465
3466                 switch (ics_getting_history) {
3467                   case H_FALSE:
3468                     switch (gameMode) {
3469                       case IcsIdle:
3470                       case BeginningOfGame:
3471                         /* User typed "moves" or "oldmoves" while we
3472                            were idle.  Pretend we asked for these
3473                            moves and soak them up so user can step
3474                            through them and/or save them.
3475                            */
3476                         Reset(FALSE, TRUE);
3477                         gameMode = IcsObserving;
3478                         ModeHighlight();
3479                         ics_gamenum = -1;
3480                         ics_getting_history = H_GOT_UNREQ_HEADER;
3481                         break;
3482                       case EditGame: /*?*/
3483                       case EditPosition: /*?*/
3484                         /* Should above feature work in these modes too? */
3485                         /* For now it doesn't */
3486                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3487                         break;
3488                       default:
3489                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3490                         break;
3491                     }
3492                     break;
3493                   case H_REQUESTED:
3494                     /* Is this the right one? */
3495                     if (gameInfo.white && gameInfo.black &&
3496                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3497                         strcmp(gameInfo.black, star_match[2]) == 0) {
3498                         /* All is well */
3499                         ics_getting_history = H_GOT_REQ_HEADER;
3500                     }
3501                     break;
3502                   case H_GOT_REQ_HEADER:
3503                   case H_GOT_UNREQ_HEADER:
3504                   case H_GOT_UNWANTED_HEADER:
3505                   case H_GETTING_MOVES:
3506                     /* Should not happen */
3507                     DisplayError(_("Error gathering move list: two headers"), 0);
3508                     ics_getting_history = H_FALSE;
3509                     break;
3510                 }
3511
3512                 /* Save player ratings into gameInfo if needed */
3513                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3514                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3515                     (gameInfo.whiteRating == -1 ||
3516                      gameInfo.blackRating == -1)) {
3517
3518                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3519                     gameInfo.blackRating = string_to_rating(star_match[3]);
3520                     if (appData.debugMode)
3521                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3522                               gameInfo.whiteRating, gameInfo.blackRating);
3523                 }
3524                 continue;
3525             }
3526
3527             if (looking_at(buf, &i,
3528               "* * match, initial time: * minute*, increment: * second")) {
3529                 /* Header for a move list -- second line */
3530                 /* Initial board will follow if this is a wild game */
3531                 if (gameInfo.event != NULL) free(gameInfo.event);
3532                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3533                 gameInfo.event = StrSave(str);
3534                 /* [HGM] we switched variant. Translate boards if needed. */
3535                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3536                 continue;
3537             }
3538
3539             if (looking_at(buf, &i, "Move  ")) {
3540                 /* Beginning of a move list */
3541                 switch (ics_getting_history) {
3542                   case H_FALSE:
3543                     /* Normally should not happen */
3544                     /* Maybe user hit reset while we were parsing */
3545                     break;
3546                   case H_REQUESTED:
3547                     /* Happens if we are ignoring a move list that is not
3548                      * the one we just requested.  Common if the user
3549                      * tries to observe two games without turning off
3550                      * getMoveList */
3551                     break;
3552                   case H_GETTING_MOVES:
3553                     /* Should not happen */
3554                     DisplayError(_("Error gathering move list: nested"), 0);
3555                     ics_getting_history = H_FALSE;
3556                     break;
3557                   case H_GOT_REQ_HEADER:
3558                     ics_getting_history = H_GETTING_MOVES;
3559                     started = STARTED_MOVES;
3560                     parse_pos = 0;
3561                     if (oldi > next_out) {
3562                         SendToPlayer(&buf[next_out], oldi - next_out);
3563                     }
3564                     break;
3565                   case H_GOT_UNREQ_HEADER:
3566                     ics_getting_history = H_GETTING_MOVES;
3567                     started = STARTED_MOVES_NOHIDE;
3568                     parse_pos = 0;
3569                     break;
3570                   case H_GOT_UNWANTED_HEADER:
3571                     ics_getting_history = H_FALSE;
3572                     break;
3573                 }
3574                 continue;
3575             }
3576
3577             if (looking_at(buf, &i, "% ") ||
3578                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3579                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3580                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3581                     soughtPending = FALSE;
3582                     seekGraphUp = TRUE;
3583                     DrawSeekGraph();
3584                 }
3585                 if(suppressKibitz) next_out = i;
3586                 savingComment = FALSE;
3587                 suppressKibitz = 0;
3588                 switch (started) {
3589                   case STARTED_MOVES:
3590                   case STARTED_MOVES_NOHIDE:
3591                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3592                     parse[parse_pos + i - oldi] = NULLCHAR;
3593                     ParseGameHistory(parse);
3594 #if ZIPPY
3595                     if (appData.zippyPlay && first.initDone) {
3596                         FeedMovesToProgram(&first, forwardMostMove);
3597                         if (gameMode == IcsPlayingWhite) {
3598                             if (WhiteOnMove(forwardMostMove)) {
3599                                 if (first.sendTime) {
3600                                   if (first.useColors) {
3601                                     SendToProgram("black\n", &first);
3602                                   }
3603                                   SendTimeRemaining(&first, TRUE);
3604                                 }
3605                                 if (first.useColors) {
3606                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3607                                 }
3608                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3609                                 first.maybeThinking = TRUE;
3610                             } else {
3611                                 if (first.usePlayother) {
3612                                   if (first.sendTime) {
3613                                     SendTimeRemaining(&first, TRUE);
3614                                   }
3615                                   SendToProgram("playother\n", &first);
3616                                   firstMove = FALSE;
3617                                 } else {
3618                                   firstMove = TRUE;
3619                                 }
3620                             }
3621                         } else if (gameMode == IcsPlayingBlack) {
3622                             if (!WhiteOnMove(forwardMostMove)) {
3623                                 if (first.sendTime) {
3624                                   if (first.useColors) {
3625                                     SendToProgram("white\n", &first);
3626                                   }
3627                                   SendTimeRemaining(&first, FALSE);
3628                                 }
3629                                 if (first.useColors) {
3630                                   SendToProgram("black\n", &first);
3631                                 }
3632                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3633                                 first.maybeThinking = TRUE;
3634                             } else {
3635                                 if (first.usePlayother) {
3636                                   if (first.sendTime) {
3637                                     SendTimeRemaining(&first, FALSE);
3638                                   }
3639                                   SendToProgram("playother\n", &first);
3640                                   firstMove = FALSE;
3641                                 } else {
3642                                   firstMove = TRUE;
3643                                 }
3644                             }
3645                         }
3646                     }
3647 #endif
3648                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3649                         /* Moves came from oldmoves or moves command
3650                            while we weren't doing anything else.
3651                            */
3652                         currentMove = forwardMostMove;
3653                         ClearHighlights();/*!!could figure this out*/
3654                         flipView = appData.flipView;
3655                         DrawPosition(TRUE, boards[currentMove]);
3656                         DisplayBothClocks();
3657                         snprintf(str, MSG_SIZ, "%s %s %s",
3658                                 gameInfo.white, _("vs."),  gameInfo.black);
3659                         DisplayTitle(str);
3660                         gameMode = IcsIdle;
3661                     } else {
3662                         /* Moves were history of an active game */
3663                         if (gameInfo.resultDetails != NULL) {
3664                             free(gameInfo.resultDetails);
3665                             gameInfo.resultDetails = NULL;
3666                         }
3667                     }
3668                     HistorySet(parseList, backwardMostMove,
3669                                forwardMostMove, currentMove-1);
3670                     DisplayMove(currentMove - 1);
3671                     if (started == STARTED_MOVES) next_out = i;
3672                     started = STARTED_NONE;
3673                     ics_getting_history = H_FALSE;
3674                     break;
3675
3676                   case STARTED_OBSERVE:
3677                     started = STARTED_NONE;
3678                     SendToICS(ics_prefix);
3679                     SendToICS("refresh\n");
3680                     break;
3681
3682                   default:
3683                     break;
3684                 }
3685                 if(bookHit) { // [HGM] book: simulate book reply
3686                     static char bookMove[MSG_SIZ]; // a bit generous?
3687
3688                     programStats.nodes = programStats.depth = programStats.time =
3689                     programStats.score = programStats.got_only_move = 0;
3690                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3691
3692                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3693                     strcat(bookMove, bookHit);
3694                     HandleMachineMove(bookMove, &first);
3695                 }
3696                 continue;
3697             }
3698
3699             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3700                  started == STARTED_HOLDINGS ||
3701                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3702                 /* Accumulate characters in move list or board */
3703                 parse[parse_pos++] = buf[i];
3704             }
3705
3706             /* Start of game messages.  Mostly we detect start of game
3707                when the first board image arrives.  On some versions
3708                of the ICS, though, we need to do a "refresh" after starting
3709                to observe in order to get the current board right away. */
3710             if (looking_at(buf, &i, "Adding game * to observation list")) {
3711                 started = STARTED_OBSERVE;
3712                 continue;
3713             }
3714
3715             /* Handle auto-observe */
3716             if (appData.autoObserve &&
3717                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3718                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3719                 char *player;
3720                 /* Choose the player that was highlighted, if any. */
3721                 if (star_match[0][0] == '\033' ||
3722                     star_match[1][0] != '\033') {
3723                     player = star_match[0];
3724                 } else {
3725                     player = star_match[2];
3726                 }
3727                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3728                         ics_prefix, StripHighlightAndTitle(player));
3729                 SendToICS(str);
3730
3731                 /* Save ratings from notify string */
3732                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3733                 player1Rating = string_to_rating(star_match[1]);
3734                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3735                 player2Rating = string_to_rating(star_match[3]);
3736
3737                 if (appData.debugMode)
3738                   fprintf(debugFP,
3739                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3740                           player1Name, player1Rating,
3741                           player2Name, player2Rating);
3742
3743                 continue;
3744             }
3745
3746             /* Deal with automatic examine mode after a game,
3747                and with IcsObserving -> IcsExamining transition */
3748             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3749                 looking_at(buf, &i, "has made you an examiner of game *")) {
3750
3751                 int gamenum = atoi(star_match[0]);
3752                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3753                     gamenum == ics_gamenum) {
3754                     /* We were already playing or observing this game;
3755                        no need to refetch history */
3756                     gameMode = IcsExamining;
3757                     if (pausing) {
3758                         pauseExamForwardMostMove = forwardMostMove;
3759                     } else if (currentMove < forwardMostMove) {
3760                         ForwardInner(forwardMostMove);
3761                     }
3762                 } else {
3763                     /* I don't think this case really can happen */
3764                     SendToICS(ics_prefix);
3765                     SendToICS("refresh\n");
3766                 }
3767                 continue;
3768             }
3769
3770             /* Error messages */
3771 //          if (ics_user_moved) {
3772             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3773                 if (looking_at(buf, &i, "Illegal move") ||
3774                     looking_at(buf, &i, "Not a legal move") ||
3775                     looking_at(buf, &i, "Your king is in check") ||
3776                     looking_at(buf, &i, "It isn't your turn") ||
3777                     looking_at(buf, &i, "It is not your move")) {
3778                     /* Illegal move */
3779                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3780                         currentMove = forwardMostMove-1;
3781                         DisplayMove(currentMove - 1); /* before DMError */
3782                         DrawPosition(FALSE, boards[currentMove]);
3783                         SwitchClocks(forwardMostMove-1); // [HGM] race
3784                         DisplayBothClocks();
3785                     }
3786                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3787                     ics_user_moved = 0;
3788                     continue;
3789                 }
3790             }
3791
3792             if (looking_at(buf, &i, "still have time") ||
3793                 looking_at(buf, &i, "not out of time") ||
3794                 looking_at(buf, &i, "either player is out of time") ||
3795                 looking_at(buf, &i, "has timeseal; checking")) {
3796                 /* We must have called his flag a little too soon */
3797                 whiteFlag = blackFlag = FALSE;
3798                 continue;
3799             }
3800
3801             if (looking_at(buf, &i, "added * seconds to") ||
3802                 looking_at(buf, &i, "seconds were added to")) {
3803                 /* Update the clocks */
3804                 SendToICS(ics_prefix);
3805                 SendToICS("refresh\n");
3806                 continue;
3807             }
3808
3809             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3810                 ics_clock_paused = TRUE;
3811                 StopClocks();
3812                 continue;
3813             }
3814
3815             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3816                 ics_clock_paused = FALSE;
3817                 StartClocks();
3818                 continue;
3819             }
3820
3821             /* Grab player ratings from the Creating: message.
3822                Note we have to check for the special case when
3823                the ICS inserts things like [white] or [black]. */
3824             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3825                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3826                 /* star_matches:
3827                    0    player 1 name (not necessarily white)
3828                    1    player 1 rating
3829                    2    empty, white, or black (IGNORED)
3830                    3    player 2 name (not necessarily black)
3831                    4    player 2 rating
3832
3833                    The names/ratings are sorted out when the game
3834                    actually starts (below).
3835                 */
3836                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3837                 player1Rating = string_to_rating(star_match[1]);
3838                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3839                 player2Rating = string_to_rating(star_match[4]);
3840
3841                 if (appData.debugMode)
3842                   fprintf(debugFP,
3843                           "Ratings from 'Creating:' %s %d, %s %d\n",
3844                           player1Name, player1Rating,
3845                           player2Name, player2Rating);
3846
3847                 continue;
3848             }
3849
3850             /* Improved generic start/end-of-game messages */
3851             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3852                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3853                 /* If tkind == 0: */
3854                 /* star_match[0] is the game number */
3855                 /*           [1] is the white player's name */
3856                 /*           [2] is the black player's name */
3857                 /* For end-of-game: */
3858                 /*           [3] is the reason for the game end */
3859                 /*           [4] is a PGN end game-token, preceded by " " */
3860                 /* For start-of-game: */
3861                 /*           [3] begins with "Creating" or "Continuing" */
3862                 /*           [4] is " *" or empty (don't care). */
3863                 int gamenum = atoi(star_match[0]);
3864                 char *whitename, *blackname, *why, *endtoken;
3865                 ChessMove endtype = EndOfFile;
3866
3867                 if (tkind == 0) {
3868                   whitename = star_match[1];
3869                   blackname = star_match[2];
3870                   why = star_match[3];
3871                   endtoken = star_match[4];
3872                 } else {
3873                   whitename = star_match[1];
3874                   blackname = star_match[3];
3875                   why = star_match[5];
3876                   endtoken = star_match[6];
3877                 }
3878
3879                 /* Game start messages */
3880                 if (strncmp(why, "Creating ", 9) == 0 ||
3881                     strncmp(why, "Continuing ", 11) == 0) {
3882                     gs_gamenum = gamenum;
3883                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3884                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3885                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3886 #if ZIPPY
3887                     if (appData.zippyPlay) {
3888                         ZippyGameStart(whitename, blackname);
3889                     }
3890 #endif /*ZIPPY*/
3891                     partnerBoardValid = FALSE; // [HGM] bughouse
3892                     continue;
3893                 }
3894
3895                 /* Game end messages */
3896                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3897                     ics_gamenum != gamenum) {
3898                     continue;
3899                 }
3900                 while (endtoken[0] == ' ') endtoken++;
3901                 switch (endtoken[0]) {
3902                   case '*':
3903                   default:
3904                     endtype = GameUnfinished;
3905                     break;
3906                   case '0':
3907                     endtype = BlackWins;
3908                     break;
3909                   case '1':
3910                     if (endtoken[1] == '/')
3911                       endtype = GameIsDrawn;
3912                     else
3913                       endtype = WhiteWins;
3914                     break;
3915                 }
3916                 GameEnds(endtype, why, GE_ICS);
3917 #if ZIPPY
3918                 if (appData.zippyPlay && first.initDone) {
3919                     ZippyGameEnd(endtype, why);
3920                     if (first.pr == NoProc) {
3921                       /* Start the next process early so that we'll
3922                          be ready for the next challenge */
3923                       StartChessProgram(&first);
3924                     }
3925                     /* Send "new" early, in case this command takes
3926                        a long time to finish, so that we'll be ready
3927                        for the next challenge. */
3928                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3929                     Reset(TRUE, TRUE);
3930                 }
3931 #endif /*ZIPPY*/
3932                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3933                 continue;
3934             }
3935
3936             if (looking_at(buf, &i, "Removing game * from observation") ||
3937                 looking_at(buf, &i, "no longer observing game *") ||
3938                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3939                 if (gameMode == IcsObserving &&
3940                     atoi(star_match[0]) == ics_gamenum)
3941                   {
3942                       /* icsEngineAnalyze */
3943                       if (appData.icsEngineAnalyze) {
3944                             ExitAnalyzeMode();
3945                             ModeHighlight();
3946                       }
3947                       StopClocks();
3948                       gameMode = IcsIdle;
3949                       ics_gamenum = -1;
3950                       ics_user_moved = FALSE;
3951                   }
3952                 continue;
3953             }
3954
3955             if (looking_at(buf, &i, "no longer examining game *")) {
3956                 if (gameMode == IcsExamining &&
3957                     atoi(star_match[0]) == ics_gamenum)
3958                   {
3959                       gameMode = IcsIdle;
3960                       ics_gamenum = -1;
3961                       ics_user_moved = FALSE;
3962                   }
3963                 continue;
3964             }
3965
3966             /* Advance leftover_start past any newlines we find,
3967                so only partial lines can get reparsed */
3968             if (looking_at(buf, &i, "\n")) {
3969                 prevColor = curColor;
3970                 if (curColor != ColorNormal) {
3971                     if (oldi > next_out) {
3972                         SendToPlayer(&buf[next_out], oldi - next_out);
3973                         next_out = oldi;
3974                     }
3975                     Colorize(ColorNormal, FALSE);
3976                     curColor = ColorNormal;
3977                 }
3978                 if (started == STARTED_BOARD) {
3979                     started = STARTED_NONE;
3980                     parse[parse_pos] = NULLCHAR;
3981                     ParseBoard12(parse);
3982                     ics_user_moved = 0;
3983
3984                     /* Send premove here */
3985                     if (appData.premove) {
3986                       char str[MSG_SIZ];
3987                       if (currentMove == 0 &&
3988                           gameMode == IcsPlayingWhite &&
3989                           appData.premoveWhite) {
3990                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3991                         if (appData.debugMode)
3992                           fprintf(debugFP, "Sending premove:\n");
3993                         SendToICS(str);
3994                       } else if (currentMove == 1 &&
3995                                  gameMode == IcsPlayingBlack &&
3996                                  appData.premoveBlack) {
3997                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3998                         if (appData.debugMode)
3999                           fprintf(debugFP, "Sending premove:\n");
4000                         SendToICS(str);
4001                       } else if (gotPremove) {
4002                         gotPremove = 0;
4003                         ClearPremoveHighlights();
4004                         if (appData.debugMode)
4005                           fprintf(debugFP, "Sending premove:\n");
4006                           UserMoveEvent(premoveFromX, premoveFromY,
4007                                         premoveToX, premoveToY,
4008                                         premovePromoChar);
4009                       }
4010                     }
4011
4012                     /* Usually suppress following prompt */
4013                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4014                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4015                         if (looking_at(buf, &i, "*% ")) {
4016                             savingComment = FALSE;
4017                             suppressKibitz = 0;
4018                         }
4019                     }
4020                     next_out = i;
4021                 } else if (started == STARTED_HOLDINGS) {
4022                     int gamenum;
4023                     char new_piece[MSG_SIZ];
4024                     started = STARTED_NONE;
4025                     parse[parse_pos] = NULLCHAR;
4026                     if (appData.debugMode)
4027                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4028                                                         parse, currentMove);
4029                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4030                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4031                         if (gameInfo.variant == VariantNormal) {
4032                           /* [HGM] We seem to switch variant during a game!
4033                            * Presumably no holdings were displayed, so we have
4034                            * to move the position two files to the right to
4035                            * create room for them!
4036                            */
4037                           VariantClass newVariant;
4038                           switch(gameInfo.boardWidth) { // base guess on board width
4039                                 case 9:  newVariant = VariantShogi; break;
4040                                 case 10: newVariant = VariantGreat; break;
4041                                 default: newVariant = VariantCrazyhouse; break;
4042                           }
4043                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4044                           /* Get a move list just to see the header, which
4045                              will tell us whether this is really bug or zh */
4046                           if (ics_getting_history == H_FALSE) {
4047                             ics_getting_history = H_REQUESTED;
4048                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4049                             SendToICS(str);
4050                           }
4051                         }
4052                         new_piece[0] = NULLCHAR;
4053                         sscanf(parse, "game %d white [%s black [%s <- %s",
4054                                &gamenum, white_holding, black_holding,
4055                                new_piece);
4056                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4057                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4058                         /* [HGM] copy holdings to board holdings area */
4059                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4060                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4061                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4062 #if ZIPPY
4063                         if (appData.zippyPlay && first.initDone) {
4064                             ZippyHoldings(white_holding, black_holding,
4065                                           new_piece);
4066                         }
4067 #endif /*ZIPPY*/
4068                         if (tinyLayout || smallLayout) {
4069                             char wh[16], bh[16];
4070                             PackHolding(wh, white_holding);
4071                             PackHolding(bh, black_holding);
4072                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4073                                     gameInfo.white, gameInfo.black);
4074                         } else {
4075                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4076                                     gameInfo.white, white_holding, _("vs."),
4077                                     gameInfo.black, black_holding);
4078                         }
4079                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4080                         DrawPosition(FALSE, boards[currentMove]);
4081                         DisplayTitle(str);
4082                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4083                         sscanf(parse, "game %d white [%s black [%s <- %s",
4084                                &gamenum, white_holding, black_holding,
4085                                new_piece);
4086                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4087                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4088                         /* [HGM] copy holdings to partner-board holdings area */
4089                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4090                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4091                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4092                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4093                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4094                       }
4095                     }
4096                     /* Suppress following prompt */
4097                     if (looking_at(buf, &i, "*% ")) {
4098                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4099                         savingComment = FALSE;
4100                         suppressKibitz = 0;
4101                     }
4102                     next_out = i;
4103                 }
4104                 continue;
4105             }
4106
4107             i++;                /* skip unparsed character and loop back */
4108         }
4109
4110         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4111 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4112 //          SendToPlayer(&buf[next_out], i - next_out);
4113             started != STARTED_HOLDINGS && leftover_start > next_out) {
4114             SendToPlayer(&buf[next_out], leftover_start - next_out);
4115             next_out = i;
4116         }
4117
4118         leftover_len = buf_len - leftover_start;
4119         /* if buffer ends with something we couldn't parse,
4120            reparse it after appending the next read */
4121
4122     } else if (count == 0) {
4123         RemoveInputSource(isr);
4124         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4125     } else {
4126         DisplayFatalError(_("Error reading from ICS"), error, 1);
4127     }
4128 }
4129
4130
4131 /* Board style 12 looks like this:
4132
4133    <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
4134
4135  * The "<12> " is stripped before it gets to this routine.  The two
4136  * trailing 0's (flip state and clock ticking) are later addition, and
4137  * some chess servers may not have them, or may have only the first.
4138  * Additional trailing fields may be added in the future.
4139  */
4140
4141 #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"
4142
4143 #define RELATION_OBSERVING_PLAYED    0
4144 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4145 #define RELATION_PLAYING_MYMOVE      1
4146 #define RELATION_PLAYING_NOTMYMOVE  -1
4147 #define RELATION_EXAMINING           2
4148 #define RELATION_ISOLATED_BOARD     -3
4149 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4150
4151 void
4152 ParseBoard12 (char *string)
4153 {
4154     GameMode newGameMode;
4155     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4156     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4157     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4158     char to_play, board_chars[200];
4159     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4160     char black[32], white[32];
4161     Board board;
4162     int prevMove = currentMove;
4163     int ticking = 2;
4164     ChessMove moveType;
4165     int fromX, fromY, toX, toY;
4166     char promoChar;
4167     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4168     char *bookHit = NULL; // [HGM] book
4169     Boolean weird = FALSE, reqFlag = FALSE;
4170
4171     fromX = fromY = toX = toY = -1;
4172
4173     newGame = FALSE;
4174
4175     if (appData.debugMode)
4176       fprintf(debugFP, _("Parsing board: %s\n"), string);
4177
4178     move_str[0] = NULLCHAR;
4179     elapsed_time[0] = NULLCHAR;
4180     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4181         int  i = 0, j;
4182         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4183             if(string[i] == ' ') { ranks++; files = 0; }
4184             else files++;
4185             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4186             i++;
4187         }
4188         for(j = 0; j <i; j++) board_chars[j] = string[j];
4189         board_chars[i] = '\0';
4190         string += i + 1;
4191     }
4192     n = sscanf(string, PATTERN, &to_play, &double_push,
4193                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4194                &gamenum, white, black, &relation, &basetime, &increment,
4195                &white_stren, &black_stren, &white_time, &black_time,
4196                &moveNum, str, elapsed_time, move_str, &ics_flip,
4197                &ticking);
4198
4199     if (n < 21) {
4200         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4201         DisplayError(str, 0);
4202         return;
4203     }
4204
4205     /* Convert the move number to internal form */
4206     moveNum = (moveNum - 1) * 2;
4207     if (to_play == 'B') moveNum++;
4208     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4209       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4210                         0, 1);
4211       return;
4212     }
4213
4214     switch (relation) {
4215       case RELATION_OBSERVING_PLAYED:
4216       case RELATION_OBSERVING_STATIC:
4217         if (gamenum == -1) {
4218             /* Old ICC buglet */
4219             relation = RELATION_OBSERVING_STATIC;
4220         }
4221         newGameMode = IcsObserving;
4222         break;
4223       case RELATION_PLAYING_MYMOVE:
4224       case RELATION_PLAYING_NOTMYMOVE:
4225         newGameMode =
4226           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4227             IcsPlayingWhite : IcsPlayingBlack;
4228         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4229         break;
4230       case RELATION_EXAMINING:
4231         newGameMode = IcsExamining;
4232         break;
4233       case RELATION_ISOLATED_BOARD:
4234       default:
4235         /* Just display this board.  If user was doing something else,
4236            we will forget about it until the next board comes. */
4237         newGameMode = IcsIdle;
4238         break;
4239       case RELATION_STARTING_POSITION:
4240         newGameMode = gameMode;
4241         break;
4242     }
4243
4244     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4245         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4246          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4247       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4248       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4249       static int lastBgGame = -1;
4250       char *toSqr;
4251       for (k = 0; k < ranks; k++) {
4252         for (j = 0; j < files; j++)
4253           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4254         if(gameInfo.holdingsWidth > 1) {
4255              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4256              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4257         }
4258       }
4259       CopyBoard(partnerBoard, board);
4260       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4261         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4262         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4263       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4264       if(toSqr = strchr(str, '-')) {
4265         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4266         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4267       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4268       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4269       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4270       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4271       if(twoBoards) {
4272           DisplayWhiteClock(white_time*fac, to_play == 'W');
4273           DisplayBlackClock(black_time*fac, to_play != 'W');
4274           activePartner = to_play;
4275           if(gamenum != lastBgGame) {
4276               char buf[MSG_SIZ];
4277               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4278               DisplayTitle(buf);
4279           }
4280           lastBgGame = gamenum;
4281           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4282                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4283       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4284                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4285       DisplayMessage(partnerStatus, "");
4286         partnerBoardValid = TRUE;
4287       return;
4288     }
4289
4290     if(appData.dualBoard && appData.bgObserve) {
4291         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4292             SendToICS(ics_prefix), SendToICS("pobserve\n");
4293         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4294             char buf[MSG_SIZ];
4295             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4296             SendToICS(buf);
4297         }
4298     }
4299
4300     /* Modify behavior for initial board display on move listing
4301        of wild games.
4302        */
4303     switch (ics_getting_history) {
4304       case H_FALSE:
4305       case H_REQUESTED:
4306         break;
4307       case H_GOT_REQ_HEADER:
4308       case H_GOT_UNREQ_HEADER:
4309         /* This is the initial position of the current game */
4310         gamenum = ics_gamenum;
4311         moveNum = 0;            /* old ICS bug workaround */
4312         if (to_play == 'B') {
4313           startedFromSetupPosition = TRUE;
4314           blackPlaysFirst = TRUE;
4315           moveNum = 1;
4316           if (forwardMostMove == 0) forwardMostMove = 1;
4317           if (backwardMostMove == 0) backwardMostMove = 1;
4318           if (currentMove == 0) currentMove = 1;
4319         }
4320         newGameMode = gameMode;
4321         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4322         break;
4323       case H_GOT_UNWANTED_HEADER:
4324         /* This is an initial board that we don't want */
4325         return;
4326       case H_GETTING_MOVES:
4327         /* Should not happen */
4328         DisplayError(_("Error gathering move list: extra board"), 0);
4329         ics_getting_history = H_FALSE;
4330         return;
4331     }
4332
4333    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4334                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4335                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4336      /* [HGM] We seem to have switched variant unexpectedly
4337       * Try to guess new variant from board size
4338       */
4339           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4340           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4341           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4342           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4343           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4344           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4345           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4346           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4347           /* Get a move list just to see the header, which
4348              will tell us whether this is really bug or zh */
4349           if (ics_getting_history == H_FALSE) {
4350             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4351             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4352             SendToICS(str);
4353           }
4354     }
4355
4356     /* Take action if this is the first board of a new game, or of a
4357        different game than is currently being displayed.  */
4358     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4359         relation == RELATION_ISOLATED_BOARD) {
4360
4361         /* Forget the old game and get the history (if any) of the new one */
4362         if (gameMode != BeginningOfGame) {
4363           Reset(TRUE, TRUE);
4364         }
4365         newGame = TRUE;
4366         if (appData.autoRaiseBoard) BoardToTop();
4367         prevMove = -3;
4368         if (gamenum == -1) {
4369             newGameMode = IcsIdle;
4370         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4371                    appData.getMoveList && !reqFlag) {
4372             /* Need to get game history */
4373             ics_getting_history = H_REQUESTED;
4374             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4375             SendToICS(str);
4376         }
4377
4378         /* Initially flip the board to have black on the bottom if playing
4379            black or if the ICS flip flag is set, but let the user change
4380            it with the Flip View button. */
4381         flipView = appData.autoFlipView ?
4382           (newGameMode == IcsPlayingBlack) || ics_flip :
4383           appData.flipView;
4384
4385         /* Done with values from previous mode; copy in new ones */
4386         gameMode = newGameMode;
4387         ModeHighlight();
4388         ics_gamenum = gamenum;
4389         if (gamenum == gs_gamenum) {
4390             int klen = strlen(gs_kind);
4391             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4392             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4393             gameInfo.event = StrSave(str);
4394         } else {
4395             gameInfo.event = StrSave("ICS game");
4396         }
4397         gameInfo.site = StrSave(appData.icsHost);
4398         gameInfo.date = PGNDate();
4399         gameInfo.round = StrSave("-");
4400         gameInfo.white = StrSave(white);
4401         gameInfo.black = StrSave(black);
4402         timeControl = basetime * 60 * 1000;
4403         timeControl_2 = 0;
4404         timeIncrement = increment * 1000;
4405         movesPerSession = 0;
4406         gameInfo.timeControl = TimeControlTagValue();
4407         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4408   if (appData.debugMode) {
4409     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4410     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4411     setbuf(debugFP, NULL);
4412   }
4413
4414         gameInfo.outOfBook = NULL;
4415
4416         /* Do we have the ratings? */
4417         if (strcmp(player1Name, white) == 0 &&
4418             strcmp(player2Name, black) == 0) {
4419             if (appData.debugMode)
4420               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4421                       player1Rating, player2Rating);
4422             gameInfo.whiteRating = player1Rating;
4423             gameInfo.blackRating = player2Rating;
4424         } else if (strcmp(player2Name, white) == 0 &&
4425                    strcmp(player1Name, black) == 0) {
4426             if (appData.debugMode)
4427               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4428                       player2Rating, player1Rating);
4429             gameInfo.whiteRating = player2Rating;
4430             gameInfo.blackRating = player1Rating;
4431         }
4432         player1Name[0] = player2Name[0] = NULLCHAR;
4433
4434         /* Silence shouts if requested */
4435         if (appData.quietPlay &&
4436             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4437             SendToICS(ics_prefix);
4438             SendToICS("set shout 0\n");
4439         }
4440     }
4441
4442     /* Deal with midgame name changes */
4443     if (!newGame) {
4444         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4445             if (gameInfo.white) free(gameInfo.white);
4446             gameInfo.white = StrSave(white);
4447         }
4448         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4449             if (gameInfo.black) free(gameInfo.black);
4450             gameInfo.black = StrSave(black);
4451         }
4452     }
4453
4454     /* Throw away game result if anything actually changes in examine mode */
4455     if (gameMode == IcsExamining && !newGame) {
4456         gameInfo.result = GameUnfinished;
4457         if (gameInfo.resultDetails != NULL) {
4458             free(gameInfo.resultDetails);
4459             gameInfo.resultDetails = NULL;
4460         }
4461     }
4462
4463     /* In pausing && IcsExamining mode, we ignore boards coming
4464        in if they are in a different variation than we are. */
4465     if (pauseExamInvalid) return;
4466     if (pausing && gameMode == IcsExamining) {
4467         if (moveNum <= pauseExamForwardMostMove) {
4468             pauseExamInvalid = TRUE;
4469             forwardMostMove = pauseExamForwardMostMove;
4470             return;
4471         }
4472     }
4473
4474   if (appData.debugMode) {
4475     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4476   }
4477     /* Parse the board */
4478     for (k = 0; k < ranks; k++) {
4479       for (j = 0; j < files; j++)
4480         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4481       if(gameInfo.holdingsWidth > 1) {
4482            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4483            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4484       }
4485     }
4486     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4487       board[5][BOARD_RGHT+1] = WhiteAngel;
4488       board[6][BOARD_RGHT+1] = WhiteMarshall;
4489       board[1][0] = BlackMarshall;
4490       board[2][0] = BlackAngel;
4491       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4492     }
4493     CopyBoard(boards[moveNum], board);
4494     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4495     if (moveNum == 0) {
4496         startedFromSetupPosition =
4497           !CompareBoards(board, initialPosition);
4498         if(startedFromSetupPosition)
4499             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4500     }
4501
4502     /* [HGM] Set castling rights. Take the outermost Rooks,
4503        to make it also work for FRC opening positions. Note that board12
4504        is really defective for later FRC positions, as it has no way to
4505        indicate which Rook can castle if they are on the same side of King.
4506        For the initial position we grant rights to the outermost Rooks,
4507        and remember thos rights, and we then copy them on positions
4508        later in an FRC game. This means WB might not recognize castlings with
4509        Rooks that have moved back to their original position as illegal,
4510        but in ICS mode that is not its job anyway.
4511     */
4512     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4513     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4514
4515         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4516             if(board[0][i] == WhiteRook) j = i;
4517         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4518         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4519             if(board[0][i] == WhiteRook) j = i;
4520         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4521         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4522             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4523         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4524         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4525             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4526         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4527
4528         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4529         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4530         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4531             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4532         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4533             if(board[BOARD_HEIGHT-1][k] == bKing)
4534                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4535         if(gameInfo.variant == VariantTwoKings) {
4536             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4537             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4538             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4539         }
4540     } else { int r;
4541         r = boards[moveNum][CASTLING][0] = initialRights[0];
4542         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4543         r = boards[moveNum][CASTLING][1] = initialRights[1];
4544         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4545         r = boards[moveNum][CASTLING][3] = initialRights[3];
4546         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4547         r = boards[moveNum][CASTLING][4] = initialRights[4];
4548         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4549         /* wildcastle kludge: always assume King has rights */
4550         r = boards[moveNum][CASTLING][2] = initialRights[2];
4551         r = boards[moveNum][CASTLING][5] = initialRights[5];
4552     }
4553     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4554     boards[moveNum][EP_STATUS] = EP_NONE;
4555     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4556     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4557     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4558
4559
4560     if (ics_getting_history == H_GOT_REQ_HEADER ||
4561         ics_getting_history == H_GOT_UNREQ_HEADER) {
4562         /* This was an initial position from a move list, not
4563            the current position */
4564         return;
4565     }
4566
4567     /* Update currentMove and known move number limits */
4568     newMove = newGame || moveNum > forwardMostMove;
4569
4570     if (newGame) {
4571         forwardMostMove = backwardMostMove = currentMove = moveNum;
4572         if (gameMode == IcsExamining && moveNum == 0) {
4573           /* Workaround for ICS limitation: we are not told the wild
4574              type when starting to examine a game.  But if we ask for
4575              the move list, the move list header will tell us */
4576             ics_getting_history = H_REQUESTED;
4577             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4578             SendToICS(str);
4579         }
4580     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4581                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4582 #if ZIPPY
4583         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4584         /* [HGM] applied this also to an engine that is silently watching        */
4585         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4586             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4587             gameInfo.variant == currentlyInitializedVariant) {
4588           takeback = forwardMostMove - moveNum;
4589           for (i = 0; i < takeback; i++) {
4590             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4591             SendToProgram("undo\n", &first);
4592           }
4593         }
4594 #endif
4595
4596         forwardMostMove = moveNum;
4597         if (!pausing || currentMove > forwardMostMove)
4598           currentMove = forwardMostMove;
4599     } else {
4600         /* New part of history that is not contiguous with old part */
4601         if (pausing && gameMode == IcsExamining) {
4602             pauseExamInvalid = TRUE;
4603             forwardMostMove = pauseExamForwardMostMove;
4604             return;
4605         }
4606         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4607 #if ZIPPY
4608             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4609                 // [HGM] when we will receive the move list we now request, it will be
4610                 // fed to the engine from the first move on. So if the engine is not
4611                 // in the initial position now, bring it there.
4612                 InitChessProgram(&first, 0);
4613             }
4614 #endif
4615             ics_getting_history = H_REQUESTED;
4616             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4617             SendToICS(str);
4618         }
4619         forwardMostMove = backwardMostMove = currentMove = moveNum;
4620     }
4621
4622     /* Update the clocks */
4623     if (strchr(elapsed_time, '.')) {
4624       /* Time is in ms */
4625       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4626       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4627     } else {
4628       /* Time is in seconds */
4629       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4630       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4631     }
4632
4633
4634 #if ZIPPY
4635     if (appData.zippyPlay && newGame &&
4636         gameMode != IcsObserving && gameMode != IcsIdle &&
4637         gameMode != IcsExamining)
4638       ZippyFirstBoard(moveNum, basetime, increment);
4639 #endif
4640
4641     /* Put the move on the move list, first converting
4642        to canonical algebraic form. */
4643     if (moveNum > 0) {
4644   if (appData.debugMode) {
4645     if (appData.debugMode) { int f = forwardMostMove;
4646         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4647                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4648                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4649     }
4650     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4651     fprintf(debugFP, "moveNum = %d\n", moveNum);
4652     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4653     setbuf(debugFP, NULL);
4654   }
4655         if (moveNum <= backwardMostMove) {
4656             /* We don't know what the board looked like before
4657                this move.  Punt. */
4658           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4659             strcat(parseList[moveNum - 1], " ");
4660             strcat(parseList[moveNum - 1], elapsed_time);
4661             moveList[moveNum - 1][0] = NULLCHAR;
4662         } else if (strcmp(move_str, "none") == 0) {
4663             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4664             /* Again, we don't know what the board looked like;
4665                this is really the start of the game. */
4666             parseList[moveNum - 1][0] = NULLCHAR;
4667             moveList[moveNum - 1][0] = NULLCHAR;
4668             backwardMostMove = moveNum;
4669             startedFromSetupPosition = TRUE;
4670             fromX = fromY = toX = toY = -1;
4671         } else {
4672           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4673           //                 So we parse the long-algebraic move string in stead of the SAN move
4674           int valid; char buf[MSG_SIZ], *prom;
4675
4676           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4677                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4678           // str looks something like "Q/a1-a2"; kill the slash
4679           if(str[1] == '/')
4680             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4681           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4682           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4683                 strcat(buf, prom); // long move lacks promo specification!
4684           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4685                 if(appData.debugMode)
4686                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4687                 safeStrCpy(move_str, buf, MSG_SIZ);
4688           }
4689           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4690                                 &fromX, &fromY, &toX, &toY, &promoChar)
4691                || ParseOneMove(buf, moveNum - 1, &moveType,
4692                                 &fromX, &fromY, &toX, &toY, &promoChar);
4693           // end of long SAN patch
4694           if (valid) {
4695             (void) CoordsToAlgebraic(boards[moveNum - 1],
4696                                      PosFlags(moveNum - 1),
4697                                      fromY, fromX, toY, toX, promoChar,
4698                                      parseList[moveNum-1]);
4699             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4700               case MT_NONE:
4701               case MT_STALEMATE:
4702               default:
4703                 break;
4704               case MT_CHECK:
4705                 if(gameInfo.variant != VariantShogi)
4706                     strcat(parseList[moveNum - 1], "+");
4707                 break;
4708               case MT_CHECKMATE:
4709               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4710                 strcat(parseList[moveNum - 1], "#");
4711                 break;
4712             }
4713             strcat(parseList[moveNum - 1], " ");
4714             strcat(parseList[moveNum - 1], elapsed_time);
4715             /* currentMoveString is set as a side-effect of ParseOneMove */
4716             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4717             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4718             strcat(moveList[moveNum - 1], "\n");
4719
4720             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4721                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4722               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4723                 ChessSquare old, new = boards[moveNum][k][j];
4724                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4725                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4726                   if(old == new) continue;
4727                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4728                   else if(new == WhiteWazir || new == BlackWazir) {
4729                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4730                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4731                       else boards[moveNum][k][j] = old; // preserve type of Gold
4732                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4733                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4734               }
4735           } else {
4736             /* Move from ICS was illegal!?  Punt. */
4737             if (appData.debugMode) {
4738               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4739               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4740             }
4741             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4742             strcat(parseList[moveNum - 1], " ");
4743             strcat(parseList[moveNum - 1], elapsed_time);
4744             moveList[moveNum - 1][0] = NULLCHAR;
4745             fromX = fromY = toX = toY = -1;
4746           }
4747         }
4748   if (appData.debugMode) {
4749     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4750     setbuf(debugFP, NULL);
4751   }
4752
4753 #if ZIPPY
4754         /* Send move to chess program (BEFORE animating it). */
4755         if (appData.zippyPlay && !newGame && newMove &&
4756            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4757
4758             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4759                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4760                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4761                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4762                             move_str);
4763                     DisplayError(str, 0);
4764                 } else {
4765                     if (first.sendTime) {
4766                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4767                     }
4768                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4769                     if (firstMove && !bookHit) {
4770                         firstMove = FALSE;
4771                         if (first.useColors) {
4772                           SendToProgram(gameMode == IcsPlayingWhite ?
4773                                         "white\ngo\n" :
4774                                         "black\ngo\n", &first);
4775                         } else {
4776                           SendToProgram("go\n", &first);
4777                         }
4778                         first.maybeThinking = TRUE;
4779                     }
4780                 }
4781             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4782               if (moveList[moveNum - 1][0] == NULLCHAR) {
4783                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4784                 DisplayError(str, 0);
4785               } else {
4786                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4787                 SendMoveToProgram(moveNum - 1, &first);
4788               }
4789             }
4790         }
4791 #endif
4792     }
4793
4794     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4795         /* If move comes from a remote source, animate it.  If it
4796            isn't remote, it will have already been animated. */
4797         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4798             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4799         }
4800         if (!pausing && appData.highlightLastMove) {
4801             SetHighlights(fromX, fromY, toX, toY);
4802         }
4803     }
4804
4805     /* Start the clocks */
4806     whiteFlag = blackFlag = FALSE;
4807     appData.clockMode = !(basetime == 0 && increment == 0);
4808     if (ticking == 0) {
4809       ics_clock_paused = TRUE;
4810       StopClocks();
4811     } else if (ticking == 1) {
4812       ics_clock_paused = FALSE;
4813     }
4814     if (gameMode == IcsIdle ||
4815         relation == RELATION_OBSERVING_STATIC ||
4816         relation == RELATION_EXAMINING ||
4817         ics_clock_paused)
4818       DisplayBothClocks();
4819     else
4820       StartClocks();
4821
4822     /* Display opponents and material strengths */
4823     if (gameInfo.variant != VariantBughouse &&
4824         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4825         if (tinyLayout || smallLayout) {
4826             if(gameInfo.variant == VariantNormal)
4827               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4828                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4829                     basetime, increment);
4830             else
4831               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4832                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4833                     basetime, increment, (int) gameInfo.variant);
4834         } else {
4835             if(gameInfo.variant == VariantNormal)
4836               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4837                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4838                     basetime, increment);
4839             else
4840               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4841                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4842                     basetime, increment, VariantName(gameInfo.variant));
4843         }
4844         DisplayTitle(str);
4845   if (appData.debugMode) {
4846     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4847   }
4848     }
4849
4850
4851     /* Display the board */
4852     if (!pausing && !appData.noGUI) {
4853
4854       if (appData.premove)
4855           if (!gotPremove ||
4856              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4857              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4858               ClearPremoveHighlights();
4859
4860       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4861         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4862       DrawPosition(j, boards[currentMove]);
4863
4864       DisplayMove(moveNum - 1);
4865       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4866             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4867               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4868         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4869       }
4870     }
4871
4872     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4873 #if ZIPPY
4874     if(bookHit) { // [HGM] book: simulate book reply
4875         static char bookMove[MSG_SIZ]; // a bit generous?
4876
4877         programStats.nodes = programStats.depth = programStats.time =
4878         programStats.score = programStats.got_only_move = 0;
4879         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4880
4881         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4882         strcat(bookMove, bookHit);
4883         HandleMachineMove(bookMove, &first);
4884     }
4885 #endif
4886 }
4887
4888 void
4889 GetMoveListEvent ()
4890 {
4891     char buf[MSG_SIZ];
4892     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4893         ics_getting_history = H_REQUESTED;
4894         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4895         SendToICS(buf);
4896     }
4897 }
4898
4899 void
4900 AnalysisPeriodicEvent (int force)
4901 {
4902     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4903          && !force) || !appData.periodicUpdates)
4904       return;
4905
4906     /* Send . command to Crafty to collect stats */
4907     SendToProgram(".\n", &first);
4908
4909     /* Don't send another until we get a response (this makes
4910        us stop sending to old Crafty's which don't understand
4911        the "." command (sending illegal cmds resets node count & time,
4912        which looks bad)) */
4913     programStats.ok_to_send = 0;
4914 }
4915
4916 void
4917 ics_update_width (int new_width)
4918 {
4919         ics_printf("set width %d\n", new_width);
4920 }
4921
4922 void
4923 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4924 {
4925     char buf[MSG_SIZ];
4926
4927     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4928         // null move in variant where engine does not understand it (for analysis purposes)
4929         SendBoard(cps, moveNum + 1); // send position after move in stead.
4930         return;
4931     }
4932     if (cps->useUsermove) {
4933       SendToProgram("usermove ", cps);
4934     }
4935     if (cps->useSAN) {
4936       char *space;
4937       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4938         int len = space - parseList[moveNum];
4939         memcpy(buf, parseList[moveNum], len);
4940         buf[len++] = '\n';
4941         buf[len] = NULLCHAR;
4942       } else {
4943         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4944       }
4945       SendToProgram(buf, cps);
4946     } else {
4947       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4948         AlphaRank(moveList[moveNum], 4);
4949         SendToProgram(moveList[moveNum], cps);
4950         AlphaRank(moveList[moveNum], 4); // and back
4951       } else
4952       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4953        * the engine. It would be nice to have a better way to identify castle
4954        * moves here. */
4955       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4956                                                                          && cps->useOOCastle) {
4957         int fromX = moveList[moveNum][0] - AAA;
4958         int fromY = moveList[moveNum][1] - ONE;
4959         int toX = moveList[moveNum][2] - AAA;
4960         int toY = moveList[moveNum][3] - ONE;
4961         if((boards[moveNum][fromY][fromX] == WhiteKing
4962             && boards[moveNum][toY][toX] == WhiteRook)
4963            || (boards[moveNum][fromY][fromX] == BlackKing
4964                && boards[moveNum][toY][toX] == BlackRook)) {
4965           if(toX > fromX) SendToProgram("O-O\n", cps);
4966           else SendToProgram("O-O-O\n", cps);
4967         }
4968         else SendToProgram(moveList[moveNum], cps);
4969       } else
4970       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4971         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4972           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4973           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4974                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4975         } else
4976           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4977                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4978         SendToProgram(buf, cps);
4979       }
4980       else SendToProgram(moveList[moveNum], cps);
4981       /* End of additions by Tord */
4982     }
4983
4984     /* [HGM] setting up the opening has brought engine in force mode! */
4985     /*       Send 'go' if we are in a mode where machine should play. */
4986     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4987         (gameMode == TwoMachinesPlay   ||
4988 #if ZIPPY
4989          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4990 #endif
4991          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4992         SendToProgram("go\n", cps);
4993   if (appData.debugMode) {
4994     fprintf(debugFP, "(extra)\n");
4995   }
4996     }
4997     setboardSpoiledMachineBlack = 0;
4998 }
4999
5000 void
5001 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5002 {
5003     char user_move[MSG_SIZ];
5004     char suffix[4];
5005
5006     if(gameInfo.variant == VariantSChess && promoChar) {
5007         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5008         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5009     } else suffix[0] = NULLCHAR;
5010
5011     switch (moveType) {
5012       default:
5013         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5014                 (int)moveType, fromX, fromY, toX, toY);
5015         DisplayError(user_move + strlen("say "), 0);
5016         break;
5017       case WhiteKingSideCastle:
5018       case BlackKingSideCastle:
5019       case WhiteQueenSideCastleWild:
5020       case BlackQueenSideCastleWild:
5021       /* PUSH Fabien */
5022       case WhiteHSideCastleFR:
5023       case BlackHSideCastleFR:
5024       /* POP Fabien */
5025         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5026         break;
5027       case WhiteQueenSideCastle:
5028       case BlackQueenSideCastle:
5029       case WhiteKingSideCastleWild:
5030       case BlackKingSideCastleWild:
5031       /* PUSH Fabien */
5032       case WhiteASideCastleFR:
5033       case BlackASideCastleFR:
5034       /* POP Fabien */
5035         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5036         break;
5037       case WhiteNonPromotion:
5038       case BlackNonPromotion:
5039         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5040         break;
5041       case WhitePromotion:
5042       case BlackPromotion:
5043         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5044           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5045                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5046                 PieceToChar(WhiteFerz));
5047         else if(gameInfo.variant == VariantGreat)
5048           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5049                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5050                 PieceToChar(WhiteMan));
5051         else
5052           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5053                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5054                 promoChar);
5055         break;
5056       case WhiteDrop:
5057       case BlackDrop:
5058       drop:
5059         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5060                  ToUpper(PieceToChar((ChessSquare) fromX)),
5061                  AAA + toX, ONE + toY);
5062         break;
5063       case IllegalMove:  /* could be a variant we don't quite understand */
5064         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5065       case NormalMove:
5066       case WhiteCapturesEnPassant:
5067       case BlackCapturesEnPassant:
5068         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5069                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5070         break;
5071     }
5072     SendToICS(user_move);
5073     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5074         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5075 }
5076
5077 void
5078 UploadGameEvent ()
5079 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5080     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5081     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5082     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5083       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5084       return;
5085     }
5086     if(gameMode != IcsExamining) { // is this ever not the case?
5087         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5088
5089         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5090           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5091         } else { // on FICS we must first go to general examine mode
5092           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5093         }
5094         if(gameInfo.variant != VariantNormal) {
5095             // try figure out wild number, as xboard names are not always valid on ICS
5096             for(i=1; i<=36; i++) {
5097               snprintf(buf, MSG_SIZ, "wild/%d", i);
5098                 if(StringToVariant(buf) == gameInfo.variant) break;
5099             }
5100             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5101             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5102             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5103         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5104         SendToICS(ics_prefix);
5105         SendToICS(buf);
5106         if(startedFromSetupPosition || backwardMostMove != 0) {
5107           fen = PositionToFEN(backwardMostMove, NULL);
5108           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5109             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5110             SendToICS(buf);
5111           } else { // FICS: everything has to set by separate bsetup commands
5112             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5113             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5114             SendToICS(buf);
5115             if(!WhiteOnMove(backwardMostMove)) {
5116                 SendToICS("bsetup tomove black\n");
5117             }
5118             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5119             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5120             SendToICS(buf);
5121             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5122             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5123             SendToICS(buf);
5124             i = boards[backwardMostMove][EP_STATUS];
5125             if(i >= 0) { // set e.p.
5126               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5127                 SendToICS(buf);
5128             }
5129             bsetup++;
5130           }
5131         }
5132       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5133             SendToICS("bsetup done\n"); // switch to normal examining.
5134     }
5135     for(i = backwardMostMove; i<last; i++) {
5136         char buf[20];
5137         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5138         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5139             int len = strlen(moveList[i]);
5140             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5141             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5142         }
5143         SendToICS(buf);
5144     }
5145     SendToICS(ics_prefix);
5146     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5147 }
5148
5149 void
5150 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5151 {
5152     if (rf == DROP_RANK) {
5153       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5154       sprintf(move, "%c@%c%c\n",
5155                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5156     } else {
5157         if (promoChar == 'x' || promoChar == NULLCHAR) {
5158           sprintf(move, "%c%c%c%c\n",
5159                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5160         } else {
5161             sprintf(move, "%c%c%c%c%c\n",
5162                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5163         }
5164     }
5165 }
5166
5167 void
5168 ProcessICSInitScript (FILE *f)
5169 {
5170     char buf[MSG_SIZ];
5171
5172     while (fgets(buf, MSG_SIZ, f)) {
5173         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5174     }
5175
5176     fclose(f);
5177 }
5178
5179
5180 static int lastX, lastY, selectFlag, dragging;
5181
5182 void
5183 Sweep (int step)
5184 {
5185     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5186     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5187     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5188     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5189     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5190     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5191     do {
5192         promoSweep -= step;
5193         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5194         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5195         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5196         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5197         if(!step) step = -1;
5198     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5199             appData.testLegality && (promoSweep == king ||
5200             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5201     if(toX >= 0) {
5202         int victim = boards[currentMove][toY][toX];
5203         boards[currentMove][toY][toX] = promoSweep;
5204         DrawPosition(FALSE, boards[currentMove]);
5205         boards[currentMove][toY][toX] = victim;
5206     } else
5207     ChangeDragPiece(promoSweep);
5208 }
5209
5210 int
5211 PromoScroll (int x, int y)
5212 {
5213   int step = 0;
5214
5215   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5216   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5217   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5218   if(!step) return FALSE;
5219   lastX = x; lastY = y;
5220   if((promoSweep < BlackPawn) == flipView) step = -step;
5221   if(step > 0) selectFlag = 1;
5222   if(!selectFlag) Sweep(step);
5223   return FALSE;
5224 }
5225
5226 void
5227 NextPiece (int step)
5228 {
5229     ChessSquare piece = boards[currentMove][toY][toX];
5230     do {
5231         pieceSweep -= step;
5232         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5233         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5234         if(!step) step = -1;
5235     } while(PieceToChar(pieceSweep) == '.');
5236     boards[currentMove][toY][toX] = pieceSweep;
5237     DrawPosition(FALSE, boards[currentMove]);
5238     boards[currentMove][toY][toX] = piece;
5239 }
5240 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5241 void
5242 AlphaRank (char *move, int n)
5243 {
5244 //    char *p = move, c; int x, y;
5245
5246     if (appData.debugMode) {
5247         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5248     }
5249
5250     if(move[1]=='*' &&
5251        move[2]>='0' && move[2]<='9' &&
5252        move[3]>='a' && move[3]<='x'    ) {
5253         move[1] = '@';
5254         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5255         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5256     } else
5257     if(move[0]>='0' && move[0]<='9' &&
5258        move[1]>='a' && move[1]<='x' &&
5259        move[2]>='0' && move[2]<='9' &&
5260        move[3]>='a' && move[3]<='x'    ) {
5261         /* input move, Shogi -> normal */
5262         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5263         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5264         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5265         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5266     } else
5267     if(move[1]=='@' &&
5268        move[3]>='0' && move[3]<='9' &&
5269        move[2]>='a' && move[2]<='x'    ) {
5270         move[1] = '*';
5271         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5272         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5273     } else
5274     if(
5275        move[0]>='a' && move[0]<='x' &&
5276        move[3]>='0' && move[3]<='9' &&
5277        move[2]>='a' && move[2]<='x'    ) {
5278          /* output move, normal -> Shogi */
5279         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5280         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5281         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5282         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5283         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5284     }
5285     if (appData.debugMode) {
5286         fprintf(debugFP, "   out = '%s'\n", move);
5287     }
5288 }
5289
5290 char yy_textstr[8000];
5291
5292 /* Parser for moves from gnuchess, ICS, or user typein box */
5293 Boolean
5294 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5295 {
5296     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5297
5298     switch (*moveType) {
5299       case WhitePromotion:
5300       case BlackPromotion:
5301       case WhiteNonPromotion:
5302       case BlackNonPromotion:
5303       case NormalMove:
5304       case WhiteCapturesEnPassant:
5305       case BlackCapturesEnPassant:
5306       case WhiteKingSideCastle:
5307       case WhiteQueenSideCastle:
5308       case BlackKingSideCastle:
5309       case BlackQueenSideCastle:
5310       case WhiteKingSideCastleWild:
5311       case WhiteQueenSideCastleWild:
5312       case BlackKingSideCastleWild:
5313       case BlackQueenSideCastleWild:
5314       /* Code added by Tord: */
5315       case WhiteHSideCastleFR:
5316       case WhiteASideCastleFR:
5317       case BlackHSideCastleFR:
5318       case BlackASideCastleFR:
5319       /* End of code added by Tord */
5320       case IllegalMove:         /* bug or odd chess variant */
5321         *fromX = currentMoveString[0] - AAA;
5322         *fromY = currentMoveString[1] - ONE;
5323         *toX = currentMoveString[2] - AAA;
5324         *toY = currentMoveString[3] - ONE;
5325         *promoChar = currentMoveString[4];
5326         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5327             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5328     if (appData.debugMode) {
5329         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5330     }
5331             *fromX = *fromY = *toX = *toY = 0;
5332             return FALSE;
5333         }
5334         if (appData.testLegality) {
5335           return (*moveType != IllegalMove);
5336         } else {
5337           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5338                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5339         }
5340
5341       case WhiteDrop:
5342       case BlackDrop:
5343         *fromX = *moveType == WhiteDrop ?
5344           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5345           (int) CharToPiece(ToLower(currentMoveString[0]));
5346         *fromY = DROP_RANK;
5347         *toX = currentMoveString[2] - AAA;
5348         *toY = currentMoveString[3] - ONE;
5349         *promoChar = NULLCHAR;
5350         return TRUE;
5351
5352       case AmbiguousMove:
5353       case ImpossibleMove:
5354       case EndOfFile:
5355       case ElapsedTime:
5356       case Comment:
5357       case PGNTag:
5358       case NAG:
5359       case WhiteWins:
5360       case BlackWins:
5361       case GameIsDrawn:
5362       default:
5363     if (appData.debugMode) {
5364         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5365     }
5366         /* bug? */
5367         *fromX = *fromY = *toX = *toY = 0;
5368         *promoChar = NULLCHAR;
5369         return FALSE;
5370     }
5371 }
5372
5373 Boolean pushed = FALSE;
5374 char *lastParseAttempt;
5375
5376 void
5377 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5378 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5379   int fromX, fromY, toX, toY; char promoChar;
5380   ChessMove moveType;
5381   Boolean valid;
5382   int nr = 0;
5383
5384   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5385     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5386     pushed = TRUE;
5387   }
5388   endPV = forwardMostMove;
5389   do {
5390     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5391     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5392     lastParseAttempt = pv;
5393     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5394     if(!valid && nr == 0 &&
5395        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5396         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5397         // Hande case where played move is different from leading PV move
5398         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5399         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5400         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5401         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5402           endPV += 2; // if position different, keep this
5403           moveList[endPV-1][0] = fromX + AAA;
5404           moveList[endPV-1][1] = fromY + ONE;
5405           moveList[endPV-1][2] = toX + AAA;
5406           moveList[endPV-1][3] = toY + ONE;
5407           parseList[endPV-1][0] = NULLCHAR;
5408           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5409         }
5410       }
5411     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5412     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5413     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5414     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5415         valid++; // allow comments in PV
5416         continue;
5417     }
5418     nr++;
5419     if(endPV+1 > framePtr) break; // no space, truncate
5420     if(!valid) break;
5421     endPV++;
5422     CopyBoard(boards[endPV], boards[endPV-1]);
5423     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5424     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5425     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5426     CoordsToAlgebraic(boards[endPV - 1],
5427                              PosFlags(endPV - 1),
5428                              fromY, fromX, toY, toX, promoChar,
5429                              parseList[endPV - 1]);
5430   } while(valid);
5431   if(atEnd == 2) return; // used hidden, for PV conversion
5432   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5433   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5434   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5435                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5436   DrawPosition(TRUE, boards[currentMove]);
5437 }
5438
5439 int
5440 MultiPV (ChessProgramState *cps)
5441 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5442         int i;
5443         for(i=0; i<cps->nrOptions; i++)
5444             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5445                 return i;
5446         return -1;
5447 }
5448
5449 Boolean
5450 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5451 {
5452         int startPV, multi, lineStart, origIndex = index;
5453         char *p, buf2[MSG_SIZ];
5454
5455         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5456         lastX = x; lastY = y;
5457         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5458         lineStart = startPV = index;
5459         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5460         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5461         index = startPV;
5462         do{ while(buf[index] && buf[index] != '\n') index++;
5463         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5464         buf[index] = 0;
5465         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5466                 int n = first.option[multi].value;
5467                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5468                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5469                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5470                 first.option[multi].value = n;
5471                 *start = *end = 0;
5472                 return FALSE;
5473         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5474                 ExcludeClick(origIndex - lineStart);
5475                 return FALSE;
5476         }
5477         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5478         *start = startPV; *end = index-1;
5479         return TRUE;
5480 }
5481
5482 char *
5483 PvToSAN (char *pv)
5484 {
5485         static char buf[10*MSG_SIZ];
5486         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5487         *buf = NULLCHAR;
5488         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5489         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5490         for(i = forwardMostMove; i<endPV; i++){
5491             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5492             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5493             k += strlen(buf+k);
5494         }
5495         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5496         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5497         endPV = savedEnd;
5498         return buf;
5499 }
5500
5501 Boolean
5502 LoadPV (int x, int y)
5503 { // called on right mouse click to load PV
5504   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5505   lastX = x; lastY = y;
5506   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5507   return TRUE;
5508 }
5509
5510 void
5511 UnLoadPV ()
5512 {
5513   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5514   if(endPV < 0) return;
5515   if(appData.autoCopyPV) CopyFENToClipboard();
5516   endPV = -1;
5517   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5518         Boolean saveAnimate = appData.animate;
5519         if(pushed) {
5520             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5521                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5522             } else storedGames--; // abandon shelved tail of original game
5523         }
5524         pushed = FALSE;
5525         forwardMostMove = currentMove;
5526         currentMove = oldFMM;
5527         appData.animate = FALSE;
5528         ToNrEvent(forwardMostMove);
5529         appData.animate = saveAnimate;
5530   }
5531   currentMove = forwardMostMove;
5532   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5533   ClearPremoveHighlights();
5534   DrawPosition(TRUE, boards[currentMove]);
5535 }
5536
5537 void
5538 MovePV (int x, int y, int h)
5539 { // step through PV based on mouse coordinates (called on mouse move)
5540   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5541
5542   // we must somehow check if right button is still down (might be released off board!)
5543   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5544   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5545   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5546   if(!step) return;
5547   lastX = x; lastY = y;
5548
5549   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5550   if(endPV < 0) return;
5551   if(y < margin) step = 1; else
5552   if(y > h - margin) step = -1;
5553   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5554   currentMove += step;
5555   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5556   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5557                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5558   DrawPosition(FALSE, boards[currentMove]);
5559 }
5560
5561
5562 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5563 // All positions will have equal probability, but the current method will not provide a unique
5564 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5565 #define DARK 1
5566 #define LITE 2
5567 #define ANY 3
5568
5569 int squaresLeft[4];
5570 int piecesLeft[(int)BlackPawn];
5571 int seed, nrOfShuffles;
5572
5573 void
5574 GetPositionNumber ()
5575 {       // sets global variable seed
5576         int i;
5577
5578         seed = appData.defaultFrcPosition;
5579         if(seed < 0) { // randomize based on time for negative FRC position numbers
5580                 for(i=0; i<50; i++) seed += random();
5581                 seed = random() ^ random() >> 8 ^ random() << 8;
5582                 if(seed<0) seed = -seed;
5583         }
5584 }
5585
5586 int
5587 put (Board board, int pieceType, int rank, int n, int shade)
5588 // put the piece on the (n-1)-th empty squares of the given shade
5589 {
5590         int i;
5591
5592         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5593                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5594                         board[rank][i] = (ChessSquare) pieceType;
5595                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5596                         squaresLeft[ANY]--;
5597                         piecesLeft[pieceType]--;
5598                         return i;
5599                 }
5600         }
5601         return -1;
5602 }
5603
5604
5605 void
5606 AddOnePiece (Board board, int pieceType, int rank, int shade)
5607 // calculate where the next piece goes, (any empty square), and put it there
5608 {
5609         int i;
5610
5611         i = seed % squaresLeft[shade];
5612         nrOfShuffles *= squaresLeft[shade];
5613         seed /= squaresLeft[shade];
5614         put(board, pieceType, rank, i, shade);
5615 }
5616
5617 void
5618 AddTwoPieces (Board board, int pieceType, int rank)
5619 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5620 {
5621         int i, n=squaresLeft[ANY], j=n-1, k;
5622
5623         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5624         i = seed % k;  // pick one
5625         nrOfShuffles *= k;
5626         seed /= k;
5627         while(i >= j) i -= j--;
5628         j = n - 1 - j; i += j;
5629         put(board, pieceType, rank, j, ANY);
5630         put(board, pieceType, rank, i, ANY);
5631 }
5632
5633 void
5634 SetUpShuffle (Board board, int number)
5635 {
5636         int i, p, first=1;
5637
5638         GetPositionNumber(); nrOfShuffles = 1;
5639
5640         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5641         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5642         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5643
5644         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5645
5646         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5647             p = (int) board[0][i];
5648             if(p < (int) BlackPawn) piecesLeft[p] ++;
5649             board[0][i] = EmptySquare;
5650         }
5651
5652         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5653             // shuffles restricted to allow normal castling put KRR first
5654             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5655                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5656             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5657                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5658             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5659                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5660             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5661                 put(board, WhiteRook, 0, 0, ANY);
5662             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5663         }
5664
5665         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5666             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5667             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5668                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5669                 while(piecesLeft[p] >= 2) {
5670                     AddOnePiece(board, p, 0, LITE);
5671                     AddOnePiece(board, p, 0, DARK);
5672                 }
5673                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5674             }
5675
5676         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5677             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5678             // but we leave King and Rooks for last, to possibly obey FRC restriction
5679             if(p == (int)WhiteRook) continue;
5680             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5681             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5682         }
5683
5684         // now everything is placed, except perhaps King (Unicorn) and Rooks
5685
5686         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5687             // Last King gets castling rights
5688             while(piecesLeft[(int)WhiteUnicorn]) {
5689                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5690                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5691             }
5692
5693             while(piecesLeft[(int)WhiteKing]) {
5694                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5695                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5696             }
5697
5698
5699         } else {
5700             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5701             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5702         }
5703
5704         // Only Rooks can be left; simply place them all
5705         while(piecesLeft[(int)WhiteRook]) {
5706                 i = put(board, WhiteRook, 0, 0, ANY);
5707                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5708                         if(first) {
5709                                 first=0;
5710                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5711                         }
5712                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5713                 }
5714         }
5715         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5716             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5717         }
5718
5719         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5720 }
5721
5722 int
5723 SetCharTable (char *table, const char * map)
5724 /* [HGM] moved here from winboard.c because of its general usefulness */
5725 /*       Basically a safe strcpy that uses the last character as King */
5726 {
5727     int result = FALSE; int NrPieces;
5728
5729     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5730                     && NrPieces >= 12 && !(NrPieces&1)) {
5731         int i; /* [HGM] Accept even length from 12 to 34 */
5732
5733         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5734         for( i=0; i<NrPieces/2-1; i++ ) {
5735             table[i] = map[i];
5736             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5737         }
5738         table[(int) WhiteKing]  = map[NrPieces/2-1];
5739         table[(int) BlackKing]  = map[NrPieces-1];
5740
5741         result = TRUE;
5742     }
5743
5744     return result;
5745 }
5746
5747 void
5748 Prelude (Board board)
5749 {       // [HGM] superchess: random selection of exo-pieces
5750         int i, j, k; ChessSquare p;
5751         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5752
5753         GetPositionNumber(); // use FRC position number
5754
5755         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5756             SetCharTable(pieceToChar, appData.pieceToCharTable);
5757             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5758                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5759         }
5760
5761         j = seed%4;                 seed /= 4;
5762         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5763         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5764         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5765         j = seed%3 + (seed%3 >= j); seed /= 3;
5766         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5767         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5768         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5769         j = seed%3;                 seed /= 3;
5770         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5771         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5772         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5773         j = seed%2 + (seed%2 >= j); seed /= 2;
5774         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5775         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5776         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5777         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5778         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5779         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5780         put(board, exoPieces[0],    0, 0, ANY);
5781         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5782 }
5783
5784 void
5785 InitPosition (int redraw)
5786 {
5787     ChessSquare (* pieces)[BOARD_FILES];
5788     int i, j, pawnRow, overrule,
5789     oldx = gameInfo.boardWidth,
5790     oldy = gameInfo.boardHeight,
5791     oldh = gameInfo.holdingsWidth;
5792     static int oldv;
5793
5794     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5795
5796     /* [AS] Initialize pv info list [HGM] and game status */
5797     {
5798         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5799             pvInfoList[i].depth = 0;
5800             boards[i][EP_STATUS] = EP_NONE;
5801             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5802         }
5803
5804         initialRulePlies = 0; /* 50-move counter start */
5805
5806         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5807         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5808     }
5809
5810
5811     /* [HGM] logic here is completely changed. In stead of full positions */
5812     /* the initialized data only consist of the two backranks. The switch */
5813     /* selects which one we will use, which is than copied to the Board   */
5814     /* initialPosition, which for the rest is initialized by Pawns and    */
5815     /* empty squares. This initial position is then copied to boards[0],  */
5816     /* possibly after shuffling, so that it remains available.            */
5817
5818     gameInfo.holdingsWidth = 0; /* default board sizes */
5819     gameInfo.boardWidth    = 8;
5820     gameInfo.boardHeight   = 8;
5821     gameInfo.holdingsSize  = 0;
5822     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5823     for(i=0; i<BOARD_FILES-2; i++)
5824       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5825     initialPosition[EP_STATUS] = EP_NONE;
5826     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5827     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5828          SetCharTable(pieceNickName, appData.pieceNickNames);
5829     else SetCharTable(pieceNickName, "............");
5830     pieces = FIDEArray;
5831
5832     switch (gameInfo.variant) {
5833     case VariantFischeRandom:
5834       shuffleOpenings = TRUE;
5835     default:
5836       break;
5837     case VariantShatranj:
5838       pieces = ShatranjArray;
5839       nrCastlingRights = 0;
5840       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5841       break;
5842     case VariantMakruk:
5843       pieces = makrukArray;
5844       nrCastlingRights = 0;
5845       startedFromSetupPosition = TRUE;
5846       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5847       break;
5848     case VariantTwoKings:
5849       pieces = twoKingsArray;
5850       break;
5851     case VariantGrand:
5852       pieces = GrandArray;
5853       nrCastlingRights = 0;
5854       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5855       gameInfo.boardWidth = 10;
5856       gameInfo.boardHeight = 10;
5857       gameInfo.holdingsSize = 7;
5858       break;
5859     case VariantCapaRandom:
5860       shuffleOpenings = TRUE;
5861     case VariantCapablanca:
5862       pieces = CapablancaArray;
5863       gameInfo.boardWidth = 10;
5864       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5865       break;
5866     case VariantGothic:
5867       pieces = GothicArray;
5868       gameInfo.boardWidth = 10;
5869       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5870       break;
5871     case VariantSChess:
5872       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5873       gameInfo.holdingsSize = 7;
5874       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5875       break;
5876     case VariantJanus:
5877       pieces = JanusArray;
5878       gameInfo.boardWidth = 10;
5879       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5880       nrCastlingRights = 6;
5881         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5882         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5883         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5884         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5885         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5886         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5887       break;
5888     case VariantFalcon:
5889       pieces = FalconArray;
5890       gameInfo.boardWidth = 10;
5891       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5892       break;
5893     case VariantXiangqi:
5894       pieces = XiangqiArray;
5895       gameInfo.boardWidth  = 9;
5896       gameInfo.boardHeight = 10;
5897       nrCastlingRights = 0;
5898       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5899       break;
5900     case VariantShogi:
5901       pieces = ShogiArray;
5902       gameInfo.boardWidth  = 9;
5903       gameInfo.boardHeight = 9;
5904       gameInfo.holdingsSize = 7;
5905       nrCastlingRights = 0;
5906       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5907       break;
5908     case VariantCourier:
5909       pieces = CourierArray;
5910       gameInfo.boardWidth  = 12;
5911       nrCastlingRights = 0;
5912       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5913       break;
5914     case VariantKnightmate:
5915       pieces = KnightmateArray;
5916       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5917       break;
5918     case VariantSpartan:
5919       pieces = SpartanArray;
5920       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5921       break;
5922     case VariantFairy:
5923       pieces = fairyArray;
5924       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5925       break;
5926     case VariantGreat:
5927       pieces = GreatArray;
5928       gameInfo.boardWidth = 10;
5929       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5930       gameInfo.holdingsSize = 8;
5931       break;
5932     case VariantSuper:
5933       pieces = FIDEArray;
5934       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5935       gameInfo.holdingsSize = 8;
5936       startedFromSetupPosition = TRUE;
5937       break;
5938     case VariantCrazyhouse:
5939     case VariantBughouse:
5940       pieces = FIDEArray;
5941       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5942       gameInfo.holdingsSize = 5;
5943       break;
5944     case VariantWildCastle:
5945       pieces = FIDEArray;
5946       /* !!?shuffle with kings guaranteed to be on d or e file */
5947       shuffleOpenings = 1;
5948       break;
5949     case VariantNoCastle:
5950       pieces = FIDEArray;
5951       nrCastlingRights = 0;
5952       /* !!?unconstrained back-rank shuffle */
5953       shuffleOpenings = 1;
5954       break;
5955     }
5956
5957     overrule = 0;
5958     if(appData.NrFiles >= 0) {
5959         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5960         gameInfo.boardWidth = appData.NrFiles;
5961     }
5962     if(appData.NrRanks >= 0) {
5963         gameInfo.boardHeight = appData.NrRanks;
5964     }
5965     if(appData.holdingsSize >= 0) {
5966         i = appData.holdingsSize;
5967         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5968         gameInfo.holdingsSize = i;
5969     }
5970     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5971     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5972         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5973
5974     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5975     if(pawnRow < 1) pawnRow = 1;
5976     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5977
5978     /* User pieceToChar list overrules defaults */
5979     if(appData.pieceToCharTable != NULL)
5980         SetCharTable(pieceToChar, appData.pieceToCharTable);
5981
5982     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5983
5984         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5985             s = (ChessSquare) 0; /* account holding counts in guard band */
5986         for( i=0; i<BOARD_HEIGHT; i++ )
5987             initialPosition[i][j] = s;
5988
5989         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5990         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5991         initialPosition[pawnRow][j] = WhitePawn;
5992         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5993         if(gameInfo.variant == VariantXiangqi) {
5994             if(j&1) {
5995                 initialPosition[pawnRow][j] =
5996                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5997                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5998                    initialPosition[2][j] = WhiteCannon;
5999                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6000                 }
6001             }
6002         }
6003         if(gameInfo.variant == VariantGrand) {
6004             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6005                initialPosition[0][j] = WhiteRook;
6006                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6007             }
6008         }
6009         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6010     }
6011     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6012
6013             j=BOARD_LEFT+1;
6014             initialPosition[1][j] = WhiteBishop;
6015             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6016             j=BOARD_RGHT-2;
6017             initialPosition[1][j] = WhiteRook;
6018             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6019     }
6020
6021     if( nrCastlingRights == -1) {
6022         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6023         /*       This sets default castling rights from none to normal corners   */
6024         /* Variants with other castling rights must set them themselves above    */
6025         nrCastlingRights = 6;
6026
6027         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6028         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6029         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6030         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6031         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6032         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6033      }
6034
6035      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6036      if(gameInfo.variant == VariantGreat) { // promotion commoners
6037         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6038         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6039         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6040         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6041      }
6042      if( gameInfo.variant == VariantSChess ) {
6043       initialPosition[1][0] = BlackMarshall;
6044       initialPosition[2][0] = BlackAngel;
6045       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6046       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6047       initialPosition[1][1] = initialPosition[2][1] = 
6048       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6049      }
6050   if (appData.debugMode) {
6051     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6052   }
6053     if(shuffleOpenings) {
6054         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6055         startedFromSetupPosition = TRUE;
6056     }
6057     if(startedFromPositionFile) {
6058       /* [HGM] loadPos: use PositionFile for every new game */
6059       CopyBoard(initialPosition, filePosition);
6060       for(i=0; i<nrCastlingRights; i++)
6061           initialRights[i] = filePosition[CASTLING][i];
6062       startedFromSetupPosition = TRUE;
6063     }
6064
6065     CopyBoard(boards[0], initialPosition);
6066
6067     if(oldx != gameInfo.boardWidth ||
6068        oldy != gameInfo.boardHeight ||
6069        oldv != gameInfo.variant ||
6070        oldh != gameInfo.holdingsWidth
6071                                          )
6072             InitDrawingSizes(-2 ,0);
6073
6074     oldv = gameInfo.variant;
6075     if (redraw)
6076       DrawPosition(TRUE, boards[currentMove]);
6077 }
6078
6079 void
6080 SendBoard (ChessProgramState *cps, int moveNum)
6081 {
6082     char message[MSG_SIZ];
6083
6084     if (cps->useSetboard) {
6085       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6086       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6087       SendToProgram(message, cps);
6088       free(fen);
6089
6090     } else {
6091       ChessSquare *bp;
6092       int i, j, left=0, right=BOARD_WIDTH;
6093       /* Kludge to set black to move, avoiding the troublesome and now
6094        * deprecated "black" command.
6095        */
6096       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6097         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6098
6099       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6100
6101       SendToProgram("edit\n", cps);
6102       SendToProgram("#\n", cps);
6103       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6104         bp = &boards[moveNum][i][left];
6105         for (j = left; j < right; j++, bp++) {
6106           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6107           if ((int) *bp < (int) BlackPawn) {
6108             if(j == BOARD_RGHT+1)
6109                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6110             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6111             if(message[0] == '+' || message[0] == '~') {
6112               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6113                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6114                         AAA + j, ONE + i);
6115             }
6116             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6117                 message[1] = BOARD_RGHT   - 1 - j + '1';
6118                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6119             }
6120             SendToProgram(message, cps);
6121           }
6122         }
6123       }
6124
6125       SendToProgram("c\n", cps);
6126       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6127         bp = &boards[moveNum][i][left];
6128         for (j = left; j < right; j++, bp++) {
6129           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6130           if (((int) *bp != (int) EmptySquare)
6131               && ((int) *bp >= (int) BlackPawn)) {
6132             if(j == BOARD_LEFT-2)
6133                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6134             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6135                     AAA + j, ONE + i);
6136             if(message[0] == '+' || message[0] == '~') {
6137               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6138                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6139                         AAA + j, ONE + i);
6140             }
6141             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6142                 message[1] = BOARD_RGHT   - 1 - j + '1';
6143                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6144             }
6145             SendToProgram(message, cps);
6146           }
6147         }
6148       }
6149
6150       SendToProgram(".\n", cps);
6151     }
6152     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6153 }
6154
6155 char exclusionHeader[MSG_SIZ];
6156 int exCnt, excludePtr;
6157 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6158 static Exclusion excluTab[200];
6159 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6160
6161 static void
6162 WriteMap (int s)
6163 {
6164     int j;
6165     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6166     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6167 }
6168
6169 static void
6170 ClearMap ()
6171 {
6172     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6173     excludePtr = 24; exCnt = 0;
6174     WriteMap(0);
6175 }
6176
6177 static void
6178 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6179 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6180     char buf[2*MOVE_LEN], *p;
6181     Exclusion *e = excluTab;
6182     int i;
6183     for(i=0; i<exCnt; i++)
6184         if(e[i].ff == fromX && e[i].fr == fromY &&
6185            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6186     if(i == exCnt) { // was not in exclude list; add it
6187         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6188         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6189             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6190             return; // abort
6191         }
6192         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6193         excludePtr++; e[i].mark = excludePtr++;
6194         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6195         exCnt++;
6196     }
6197     exclusionHeader[e[i].mark] = state;
6198 }
6199
6200 static int
6201 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6202 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6203     char buf[MSG_SIZ];
6204     int j, k;
6205     ChessMove moveType;
6206     if((signed char)promoChar == -1) { // kludge to indicate best move
6207         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6208             return 1; // if unparsable, abort
6209     }
6210     // update exclusion map (resolving toggle by consulting existing state)
6211     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6212     j = k%8; k >>= 3;
6213     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6214     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6215          excludeMap[k] |=   1<<j;
6216     else excludeMap[k] &= ~(1<<j);
6217     // update header
6218     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6219     // inform engine
6220     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6221     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6222     SendToProgram(buf, &first);
6223     return (state == '+');
6224 }
6225
6226 static void
6227 ExcludeClick (int index)
6228 {
6229     int i, j;
6230     Exclusion *e = excluTab;
6231     if(index < 25) { // none, best or tail clicked
6232         if(index < 13) { // none: include all
6233             WriteMap(0); // clear map
6234             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6235             SendToProgram("include all\n", &first); // and inform engine
6236         } else if(index > 18) { // tail
6237             if(exclusionHeader[19] == '-') { // tail was excluded
6238                 SendToProgram("include all\n", &first);
6239                 WriteMap(0); // clear map completely
6240                 // now re-exclude selected moves
6241                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6242                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6243             } else { // tail was included or in mixed state
6244                 SendToProgram("exclude all\n", &first);
6245                 WriteMap(0xFF); // fill map completely
6246                 // now re-include selected moves
6247                 j = 0; // count them
6248                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6249                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6250                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6251             }
6252         } else { // best
6253             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6254         }
6255     } else {
6256         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6257             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6258             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6259             break;
6260         }
6261     }
6262 }
6263
6264 ChessSquare
6265 DefaultPromoChoice (int white)
6266 {
6267     ChessSquare result;
6268     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6269         result = WhiteFerz; // no choice
6270     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6271         result= WhiteKing; // in Suicide Q is the last thing we want
6272     else if(gameInfo.variant == VariantSpartan)
6273         result = white ? WhiteQueen : WhiteAngel;
6274     else result = WhiteQueen;
6275     if(!white) result = WHITE_TO_BLACK result;
6276     return result;
6277 }
6278
6279 static int autoQueen; // [HGM] oneclick
6280
6281 int
6282 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6283 {
6284     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6285     /* [HGM] add Shogi promotions */
6286     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6287     ChessSquare piece;
6288     ChessMove moveType;
6289     Boolean premove;
6290
6291     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6292     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6293
6294     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6295       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6296         return FALSE;
6297
6298     piece = boards[currentMove][fromY][fromX];
6299     if(gameInfo.variant == VariantShogi) {
6300         promotionZoneSize = BOARD_HEIGHT/3;
6301         highestPromotingPiece = (int)WhiteFerz;
6302     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6303         promotionZoneSize = 3;
6304     }
6305
6306     // Treat Lance as Pawn when it is not representing Amazon
6307     if(gameInfo.variant != VariantSuper) {
6308         if(piece == WhiteLance) piece = WhitePawn; else
6309         if(piece == BlackLance) piece = BlackPawn;
6310     }
6311
6312     // next weed out all moves that do not touch the promotion zone at all
6313     if((int)piece >= BlackPawn) {
6314         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6315              return FALSE;
6316         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6317     } else {
6318         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6319            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6320     }
6321
6322     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6323
6324     // weed out mandatory Shogi promotions
6325     if(gameInfo.variant == VariantShogi) {
6326         if(piece >= BlackPawn) {
6327             if(toY == 0 && piece == BlackPawn ||
6328                toY == 0 && piece == BlackQueen ||
6329                toY <= 1 && piece == BlackKnight) {
6330                 *promoChoice = '+';
6331                 return FALSE;
6332             }
6333         } else {
6334             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6335                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6336                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6337                 *promoChoice = '+';
6338                 return FALSE;
6339             }
6340         }
6341     }
6342
6343     // weed out obviously illegal Pawn moves
6344     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6345         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6346         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6347         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6348         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6349         // note we are not allowed to test for valid (non-)capture, due to premove
6350     }
6351
6352     // we either have a choice what to promote to, or (in Shogi) whether to promote
6353     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6354         *promoChoice = PieceToChar(BlackFerz);  // no choice
6355         return FALSE;
6356     }
6357     // no sense asking what we must promote to if it is going to explode...
6358     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6359         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6360         return FALSE;
6361     }
6362     // give caller the default choice even if we will not make it
6363     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6364     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6365     if(        sweepSelect && gameInfo.variant != VariantGreat
6366                            && gameInfo.variant != VariantGrand
6367                            && gameInfo.variant != VariantSuper) return FALSE;
6368     if(autoQueen) return FALSE; // predetermined
6369
6370     // suppress promotion popup on illegal moves that are not premoves
6371     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6372               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6373     if(appData.testLegality && !premove) {
6374         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6375                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6376         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6377             return FALSE;
6378     }
6379
6380     return TRUE;
6381 }
6382
6383 int
6384 InPalace (int row, int column)
6385 {   /* [HGM] for Xiangqi */
6386     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6387          column < (BOARD_WIDTH + 4)/2 &&
6388          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6389     return FALSE;
6390 }
6391
6392 int
6393 PieceForSquare (int x, int y)
6394 {
6395   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6396      return -1;
6397   else
6398      return boards[currentMove][y][x];
6399 }
6400
6401 int
6402 OKToStartUserMove (int x, int y)
6403 {
6404     ChessSquare from_piece;
6405     int white_piece;
6406
6407     if (matchMode) return FALSE;
6408     if (gameMode == EditPosition) return TRUE;
6409
6410     if (x >= 0 && y >= 0)
6411       from_piece = boards[currentMove][y][x];
6412     else
6413       from_piece = EmptySquare;
6414
6415     if (from_piece == EmptySquare) return FALSE;
6416
6417     white_piece = (int)from_piece >= (int)WhitePawn &&
6418       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6419
6420     switch (gameMode) {
6421       case AnalyzeFile:
6422       case TwoMachinesPlay:
6423       case EndOfGame:
6424         return FALSE;
6425
6426       case IcsObserving:
6427       case IcsIdle:
6428         return FALSE;
6429
6430       case MachinePlaysWhite:
6431       case IcsPlayingBlack:
6432         if (appData.zippyPlay) return FALSE;
6433         if (white_piece) {
6434             DisplayMoveError(_("You are playing Black"));
6435             return FALSE;
6436         }
6437         break;
6438
6439       case MachinePlaysBlack:
6440       case IcsPlayingWhite:
6441         if (appData.zippyPlay) return FALSE;
6442         if (!white_piece) {
6443             DisplayMoveError(_("You are playing White"));
6444             return FALSE;
6445         }
6446         break;
6447
6448       case PlayFromGameFile:
6449             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6450       case EditGame:
6451         if (!white_piece && WhiteOnMove(currentMove)) {
6452             DisplayMoveError(_("It is White's turn"));
6453             return FALSE;
6454         }
6455         if (white_piece && !WhiteOnMove(currentMove)) {
6456             DisplayMoveError(_("It is Black's turn"));
6457             return FALSE;
6458         }
6459         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6460             /* Editing correspondence game history */
6461             /* Could disallow this or prompt for confirmation */
6462             cmailOldMove = -1;
6463         }
6464         break;
6465
6466       case BeginningOfGame:
6467         if (appData.icsActive) return FALSE;
6468         if (!appData.noChessProgram) {
6469             if (!white_piece) {
6470                 DisplayMoveError(_("You are playing White"));
6471                 return FALSE;
6472             }
6473         }
6474         break;
6475
6476       case Training:
6477         if (!white_piece && WhiteOnMove(currentMove)) {
6478             DisplayMoveError(_("It is White's turn"));
6479             return FALSE;
6480         }
6481         if (white_piece && !WhiteOnMove(currentMove)) {
6482             DisplayMoveError(_("It is Black's turn"));
6483             return FALSE;
6484         }
6485         break;
6486
6487       default:
6488       case IcsExamining:
6489         break;
6490     }
6491     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6492         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6493         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6494         && gameMode != AnalyzeFile && gameMode != Training) {
6495         DisplayMoveError(_("Displayed position is not current"));
6496         return FALSE;
6497     }
6498     return TRUE;
6499 }
6500
6501 Boolean
6502 OnlyMove (int *x, int *y, Boolean captures) 
6503 {
6504     DisambiguateClosure cl;
6505     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6506     switch(gameMode) {
6507       case MachinePlaysBlack:
6508       case IcsPlayingWhite:
6509       case BeginningOfGame:
6510         if(!WhiteOnMove(currentMove)) return FALSE;
6511         break;
6512       case MachinePlaysWhite:
6513       case IcsPlayingBlack:
6514         if(WhiteOnMove(currentMove)) return FALSE;
6515         break;
6516       case EditGame:
6517         break;
6518       default:
6519         return FALSE;
6520     }
6521     cl.pieceIn = EmptySquare;
6522     cl.rfIn = *y;
6523     cl.ffIn = *x;
6524     cl.rtIn = -1;
6525     cl.ftIn = -1;
6526     cl.promoCharIn = NULLCHAR;
6527     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6528     if( cl.kind == NormalMove ||
6529         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6530         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6531         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6532       fromX = cl.ff;
6533       fromY = cl.rf;
6534       *x = cl.ft;
6535       *y = cl.rt;
6536       return TRUE;
6537     }
6538     if(cl.kind != ImpossibleMove) return FALSE;
6539     cl.pieceIn = EmptySquare;
6540     cl.rfIn = -1;
6541     cl.ffIn = -1;
6542     cl.rtIn = *y;
6543     cl.ftIn = *x;
6544     cl.promoCharIn = NULLCHAR;
6545     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6546     if( cl.kind == NormalMove ||
6547         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6548         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6549         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6550       fromX = cl.ff;
6551       fromY = cl.rf;
6552       *x = cl.ft;
6553       *y = cl.rt;
6554       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6555       return TRUE;
6556     }
6557     return FALSE;
6558 }
6559
6560 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6561 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6562 int lastLoadGameUseList = FALSE;
6563 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6564 ChessMove lastLoadGameStart = EndOfFile;
6565 int doubleClick;
6566
6567 void
6568 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6569 {
6570     ChessMove moveType;
6571     ChessSquare pup;
6572     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6573
6574     /* Check if the user is playing in turn.  This is complicated because we
6575        let the user "pick up" a piece before it is his turn.  So the piece he
6576        tried to pick up may have been captured by the time he puts it down!
6577        Therefore we use the color the user is supposed to be playing in this
6578        test, not the color of the piece that is currently on the starting
6579        square---except in EditGame mode, where the user is playing both
6580        sides; fortunately there the capture race can't happen.  (It can
6581        now happen in IcsExamining mode, but that's just too bad.  The user
6582        will get a somewhat confusing message in that case.)
6583        */
6584
6585     switch (gameMode) {
6586       case AnalyzeFile:
6587       case TwoMachinesPlay:
6588       case EndOfGame:
6589       case IcsObserving:
6590       case IcsIdle:
6591         /* We switched into a game mode where moves are not accepted,
6592            perhaps while the mouse button was down. */
6593         return;
6594
6595       case MachinePlaysWhite:
6596         /* User is moving for Black */
6597         if (WhiteOnMove(currentMove)) {
6598             DisplayMoveError(_("It is White's turn"));
6599             return;
6600         }
6601         break;
6602
6603       case MachinePlaysBlack:
6604         /* User is moving for White */
6605         if (!WhiteOnMove(currentMove)) {
6606             DisplayMoveError(_("It is Black's turn"));
6607             return;
6608         }
6609         break;
6610
6611       case PlayFromGameFile:
6612             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6613       case EditGame:
6614       case IcsExamining:
6615       case BeginningOfGame:
6616       case AnalyzeMode:
6617       case Training:
6618         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6619         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6620             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6621             /* User is moving for Black */
6622             if (WhiteOnMove(currentMove)) {
6623                 DisplayMoveError(_("It is White's turn"));
6624                 return;
6625             }
6626         } else {
6627             /* User is moving for White */
6628             if (!WhiteOnMove(currentMove)) {
6629                 DisplayMoveError(_("It is Black's turn"));
6630                 return;
6631             }
6632         }
6633         break;
6634
6635       case IcsPlayingBlack:
6636         /* User is moving for Black */
6637         if (WhiteOnMove(currentMove)) {
6638             if (!appData.premove) {
6639                 DisplayMoveError(_("It is White's turn"));
6640             } else if (toX >= 0 && toY >= 0) {
6641                 premoveToX = toX;
6642                 premoveToY = toY;
6643                 premoveFromX = fromX;
6644                 premoveFromY = fromY;
6645                 premovePromoChar = promoChar;
6646                 gotPremove = 1;
6647                 if (appData.debugMode)
6648                     fprintf(debugFP, "Got premove: fromX %d,"
6649                             "fromY %d, toX %d, toY %d\n",
6650                             fromX, fromY, toX, toY);
6651             }
6652             return;
6653         }
6654         break;
6655
6656       case IcsPlayingWhite:
6657         /* User is moving for White */
6658         if (!WhiteOnMove(currentMove)) {
6659             if (!appData.premove) {
6660                 DisplayMoveError(_("It is Black's turn"));
6661             } else if (toX >= 0 && toY >= 0) {
6662                 premoveToX = toX;
6663                 premoveToY = toY;
6664                 premoveFromX = fromX;
6665                 premoveFromY = fromY;
6666                 premovePromoChar = promoChar;
6667                 gotPremove = 1;
6668                 if (appData.debugMode)
6669                     fprintf(debugFP, "Got premove: fromX %d,"
6670                             "fromY %d, toX %d, toY %d\n",
6671                             fromX, fromY, toX, toY);
6672             }
6673             return;
6674         }
6675         break;
6676
6677       default:
6678         break;
6679
6680       case EditPosition:
6681         /* EditPosition, empty square, or different color piece;
6682            click-click move is possible */
6683         if (toX == -2 || toY == -2) {
6684             boards[0][fromY][fromX] = EmptySquare;
6685             DrawPosition(FALSE, boards[currentMove]);
6686             return;
6687         } else if (toX >= 0 && toY >= 0) {
6688             boards[0][toY][toX] = boards[0][fromY][fromX];
6689             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6690                 if(boards[0][fromY][0] != EmptySquare) {
6691                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6692                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6693                 }
6694             } else
6695             if(fromX == BOARD_RGHT+1) {
6696                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6697                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6698                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6699                 }
6700             } else
6701             boards[0][fromY][fromX] = gatingPiece;
6702             DrawPosition(FALSE, boards[currentMove]);
6703             return;
6704         }
6705         return;
6706     }
6707
6708     if(toX < 0 || toY < 0) return;
6709     pup = boards[currentMove][toY][toX];
6710
6711     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6712     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6713          if( pup != EmptySquare ) return;
6714          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6715            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6716                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6717            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6718            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6719            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6720            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6721          fromY = DROP_RANK;
6722     }
6723
6724     /* [HGM] always test for legality, to get promotion info */
6725     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6726                                          fromY, fromX, toY, toX, promoChar);
6727
6728     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6729
6730     /* [HGM] but possibly ignore an IllegalMove result */
6731     if (appData.testLegality) {
6732         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6733             DisplayMoveError(_("Illegal move"));
6734             return;
6735         }
6736     }
6737
6738     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6739         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6740              ClearPremoveHighlights(); // was included
6741         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6742         return;
6743     }
6744
6745     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6746 }
6747
6748 /* Common tail of UserMoveEvent and DropMenuEvent */
6749 int
6750 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6751 {
6752     char *bookHit = 0;
6753
6754     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6755         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6756         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6757         if(WhiteOnMove(currentMove)) {
6758             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6759         } else {
6760             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6761         }
6762     }
6763
6764     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6765        move type in caller when we know the move is a legal promotion */
6766     if(moveType == NormalMove && promoChar)
6767         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6768
6769     /* [HGM] <popupFix> The following if has been moved here from
6770        UserMoveEvent(). Because it seemed to belong here (why not allow
6771        piece drops in training games?), and because it can only be
6772        performed after it is known to what we promote. */
6773     if (gameMode == Training) {
6774       /* compare the move played on the board to the next move in the
6775        * game. If they match, display the move and the opponent's response.
6776        * If they don't match, display an error message.
6777        */
6778       int saveAnimate;
6779       Board testBoard;
6780       CopyBoard(testBoard, boards[currentMove]);
6781       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6782
6783       if (CompareBoards(testBoard, boards[currentMove+1])) {
6784         ForwardInner(currentMove+1);
6785
6786         /* Autoplay the opponent's response.
6787          * if appData.animate was TRUE when Training mode was entered,
6788          * the response will be animated.
6789          */
6790         saveAnimate = appData.animate;
6791         appData.animate = animateTraining;
6792         ForwardInner(currentMove+1);
6793         appData.animate = saveAnimate;
6794
6795         /* check for the end of the game */
6796         if (currentMove >= forwardMostMove) {
6797           gameMode = PlayFromGameFile;
6798           ModeHighlight();
6799           SetTrainingModeOff();
6800           DisplayInformation(_("End of game"));
6801         }
6802       } else {
6803         DisplayError(_("Incorrect move"), 0);
6804       }
6805       return 1;
6806     }
6807
6808   /* Ok, now we know that the move is good, so we can kill
6809      the previous line in Analysis Mode */
6810   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6811                                 && currentMove < forwardMostMove) {
6812     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6813     else forwardMostMove = currentMove;
6814   }
6815
6816   ClearMap();
6817
6818   /* If we need the chess program but it's dead, restart it */
6819   ResurrectChessProgram();
6820
6821   /* A user move restarts a paused game*/
6822   if (pausing)
6823     PauseEvent();
6824
6825   thinkOutput[0] = NULLCHAR;
6826
6827   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6828
6829   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6830     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6831     return 1;
6832   }
6833
6834   if (gameMode == BeginningOfGame) {
6835     if (appData.noChessProgram) {
6836       gameMode = EditGame;
6837       SetGameInfo();
6838     } else {
6839       char buf[MSG_SIZ];
6840       gameMode = MachinePlaysBlack;
6841       StartClocks();
6842       SetGameInfo();
6843       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6844       DisplayTitle(buf);
6845       if (first.sendName) {
6846         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6847         SendToProgram(buf, &first);
6848       }
6849       StartClocks();
6850     }
6851     ModeHighlight();
6852   }
6853
6854   /* Relay move to ICS or chess engine */
6855   if (appData.icsActive) {
6856     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6857         gameMode == IcsExamining) {
6858       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6859         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6860         SendToICS("draw ");
6861         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6862       }
6863       // also send plain move, in case ICS does not understand atomic claims
6864       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6865       ics_user_moved = 1;
6866     }
6867   } else {
6868     if (first.sendTime && (gameMode == BeginningOfGame ||
6869                            gameMode == MachinePlaysWhite ||
6870                            gameMode == MachinePlaysBlack)) {
6871       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6872     }
6873     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6874          // [HGM] book: if program might be playing, let it use book
6875         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6876         first.maybeThinking = TRUE;
6877     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6878         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6879         SendBoard(&first, currentMove+1);
6880     } else SendMoveToProgram(forwardMostMove-1, &first);
6881     if (currentMove == cmailOldMove + 1) {
6882       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6883     }
6884   }
6885
6886   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6887
6888   switch (gameMode) {
6889   case EditGame:
6890     if(appData.testLegality)
6891     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6892     case MT_NONE:
6893     case MT_CHECK:
6894       break;
6895     case MT_CHECKMATE:
6896     case MT_STAINMATE:
6897       if (WhiteOnMove(currentMove)) {
6898         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6899       } else {
6900         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6901       }
6902       break;
6903     case MT_STALEMATE:
6904       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6905       break;
6906     }
6907     break;
6908
6909   case MachinePlaysBlack:
6910   case MachinePlaysWhite:
6911     /* disable certain menu options while machine is thinking */
6912     SetMachineThinkingEnables();
6913     break;
6914
6915   default:
6916     break;
6917   }
6918
6919   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6920   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6921
6922   if(bookHit) { // [HGM] book: simulate book reply
6923         static char bookMove[MSG_SIZ]; // a bit generous?
6924
6925         programStats.nodes = programStats.depth = programStats.time =
6926         programStats.score = programStats.got_only_move = 0;
6927         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6928
6929         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6930         strcat(bookMove, bookHit);
6931         HandleMachineMove(bookMove, &first);
6932   }
6933   return 1;
6934 }
6935
6936 void
6937 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6938 {
6939     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6940     Markers *m = (Markers *) closure;
6941     if(rf == fromY && ff == fromX)
6942         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6943                          || kind == WhiteCapturesEnPassant
6944                          || kind == BlackCapturesEnPassant);
6945     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6946 }
6947
6948 void
6949 MarkTargetSquares (int clear)
6950 {
6951   int x, y;
6952   if(clear) // no reason to ever suppress clearing
6953     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6954   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6955      !appData.testLegality || gameMode == EditPosition) return;
6956   if(!clear) {
6957     int capt = 0;
6958     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6959     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6960       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6961       if(capt)
6962       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6963     }
6964   }
6965   DrawPosition(FALSE, NULL);
6966 }
6967
6968 int
6969 Explode (Board board, int fromX, int fromY, int toX, int toY)
6970 {
6971     if(gameInfo.variant == VariantAtomic &&
6972        (board[toY][toX] != EmptySquare ||                     // capture?
6973         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6974                          board[fromY][fromX] == BlackPawn   )
6975       )) {
6976         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6977         return TRUE;
6978     }
6979     return FALSE;
6980 }
6981
6982 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6983
6984 int
6985 CanPromote (ChessSquare piece, int y)
6986 {
6987         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6988         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6989         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6990            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6991            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6992                                                   gameInfo.variant == VariantMakruk) return FALSE;
6993         return (piece == BlackPawn && y == 1 ||
6994                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6995                 piece == BlackLance && y == 1 ||
6996                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6997 }
6998
6999 void
7000 LeftClick (ClickType clickType, int xPix, int yPix)
7001 {
7002     int x, y;
7003     Boolean saveAnimate;
7004     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7005     char promoChoice = NULLCHAR;
7006     ChessSquare piece;
7007     static TimeMark lastClickTime, prevClickTime;
7008
7009     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7010
7011     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7012
7013     if (clickType == Press) ErrorPopDown();
7014
7015     x = EventToSquare(xPix, BOARD_WIDTH);
7016     y = EventToSquare(yPix, BOARD_HEIGHT);
7017     if (!flipView && y >= 0) {
7018         y = BOARD_HEIGHT - 1 - y;
7019     }
7020     if (flipView && x >= 0) {
7021         x = BOARD_WIDTH - 1 - x;
7022     }
7023
7024     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7025         defaultPromoChoice = promoSweep;
7026         promoSweep = EmptySquare;   // terminate sweep
7027         promoDefaultAltered = TRUE;
7028         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7029     }
7030
7031     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7032         if(clickType == Release) return; // ignore upclick of click-click destination
7033         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7034         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7035         if(gameInfo.holdingsWidth &&
7036                 (WhiteOnMove(currentMove)
7037                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7038                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7039             // click in right holdings, for determining promotion piece
7040             ChessSquare p = boards[currentMove][y][x];
7041             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7042             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7043             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7044                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7045                 fromX = fromY = -1;
7046                 return;
7047             }
7048         }
7049         DrawPosition(FALSE, boards[currentMove]);
7050         return;
7051     }
7052
7053     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7054     if(clickType == Press
7055             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7056               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7057               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7058         return;
7059
7060     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7061         // could be static click on premove from-square: abort premove
7062         gotPremove = 0;
7063         ClearPremoveHighlights();
7064     }
7065
7066     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7067         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7068
7069     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7070         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7071                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7072         defaultPromoChoice = DefaultPromoChoice(side);
7073     }
7074
7075     autoQueen = appData.alwaysPromoteToQueen;
7076
7077     if (fromX == -1) {
7078       int originalY = y;
7079       gatingPiece = EmptySquare;
7080       if (clickType != Press) {
7081         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7082             DragPieceEnd(xPix, yPix); dragging = 0;
7083             DrawPosition(FALSE, NULL);
7084         }
7085         return;
7086       }
7087       doubleClick = FALSE;
7088       fromX = x; fromY = y; toX = toY = -1;
7089       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7090          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7091          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7092             /* First square */
7093             if (OKToStartUserMove(fromX, fromY)) {
7094                 second = 0;
7095                 MarkTargetSquares(0);
7096                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7097                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7098                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7099                     promoSweep = defaultPromoChoice;
7100                     selectFlag = 0; lastX = xPix; lastY = yPix;
7101                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7102                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7103                 }
7104                 if (appData.highlightDragging) {
7105                     SetHighlights(fromX, fromY, -1, -1);
7106                 } else {
7107                     ClearHighlights();
7108                 }
7109             } else fromX = fromY = -1;
7110             return;
7111         }
7112     }
7113
7114     /* fromX != -1 */
7115     if (clickType == Press && gameMode != EditPosition) {
7116         ChessSquare fromP;
7117         ChessSquare toP;
7118         int frc;
7119
7120         // ignore off-board to clicks
7121         if(y < 0 || x < 0) return;
7122
7123         /* Check if clicking again on the same color piece */
7124         fromP = boards[currentMove][fromY][fromX];
7125         toP = boards[currentMove][y][x];
7126         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7127         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7128              WhitePawn <= toP && toP <= WhiteKing &&
7129              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7130              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7131             (BlackPawn <= fromP && fromP <= BlackKing &&
7132              BlackPawn <= toP && toP <= BlackKing &&
7133              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7134              !(fromP == BlackKing && toP == BlackRook && frc))) {
7135             /* Clicked again on same color piece -- changed his mind */
7136             second = (x == fromX && y == fromY);
7137             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7138                 second = FALSE; // first double-click rather than scond click
7139                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7140             }
7141             promoDefaultAltered = FALSE;
7142             MarkTargetSquares(1);
7143            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7144             if (appData.highlightDragging) {
7145                 SetHighlights(x, y, -1, -1);
7146             } else {
7147                 ClearHighlights();
7148             }
7149             if (OKToStartUserMove(x, y)) {
7150                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7151                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7152                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7153                  gatingPiece = boards[currentMove][fromY][fromX];
7154                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7155                 fromX = x;
7156                 fromY = y; dragging = 1;
7157                 MarkTargetSquares(0);
7158                 DragPieceBegin(xPix, yPix, FALSE);
7159                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7160                     promoSweep = defaultPromoChoice;
7161                     selectFlag = 0; lastX = xPix; lastY = yPix;
7162                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7163                 }
7164             }
7165            }
7166            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7167            second = FALSE; 
7168         }
7169         // ignore clicks on holdings
7170         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7171     }
7172
7173     if (clickType == Release && x == fromX && y == fromY) {
7174         DragPieceEnd(xPix, yPix); dragging = 0;
7175         if(clearFlag) {
7176             // a deferred attempt to click-click move an empty square on top of a piece
7177             boards[currentMove][y][x] = EmptySquare;
7178             ClearHighlights();
7179             DrawPosition(FALSE, boards[currentMove]);
7180             fromX = fromY = -1; clearFlag = 0;
7181             return;
7182         }
7183         if (appData.animateDragging) {
7184             /* Undo animation damage if any */
7185             DrawPosition(FALSE, NULL);
7186         }
7187         if (second || sweepSelecting) {
7188             /* Second up/down in same square; just abort move */
7189             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7190             second = sweepSelecting = 0;
7191             fromX = fromY = -1;
7192             gatingPiece = EmptySquare;
7193             ClearHighlights();
7194             gotPremove = 0;
7195             ClearPremoveHighlights();
7196         } else {
7197             /* First upclick in same square; start click-click mode */
7198             SetHighlights(x, y, -1, -1);
7199         }
7200         return;
7201     }
7202
7203     clearFlag = 0;
7204
7205     /* we now have a different from- and (possibly off-board) to-square */
7206     /* Completed move */
7207     if(!sweepSelecting) {
7208         toX = x;
7209         toY = y;
7210     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7211
7212     saveAnimate = appData.animate;
7213     if (clickType == Press) {
7214         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7215             // must be Edit Position mode with empty-square selected
7216             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7217             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7218             return;
7219         }
7220         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7221           if(appData.sweepSelect) {
7222             ChessSquare piece = boards[currentMove][fromY][fromX];
7223             promoSweep = defaultPromoChoice;
7224             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7225             selectFlag = 0; lastX = xPix; lastY = yPix;
7226             Sweep(0); // Pawn that is going to promote: preview promotion piece
7227             sweepSelecting = 1;
7228             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7229             MarkTargetSquares(1);
7230           }
7231           return; // promo popup appears on up-click
7232         }
7233         /* Finish clickclick move */
7234         if (appData.animate || appData.highlightLastMove) {
7235             SetHighlights(fromX, fromY, toX, toY);
7236         } else {
7237             ClearHighlights();
7238         }
7239     } else {
7240         /* Finish drag move */
7241         if (appData.highlightLastMove) {
7242             SetHighlights(fromX, fromY, toX, toY);
7243         } else {
7244             ClearHighlights();
7245         }
7246         DragPieceEnd(xPix, yPix); dragging = 0;
7247         /* Don't animate move and drag both */
7248         appData.animate = FALSE;
7249     }
7250     MarkTargetSquares(1);
7251
7252     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7253     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7254         ChessSquare piece = boards[currentMove][fromY][fromX];
7255         if(gameMode == EditPosition && piece != EmptySquare &&
7256            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7257             int n;
7258
7259             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7260                 n = PieceToNumber(piece - (int)BlackPawn);
7261                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7262                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7263                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7264             } else
7265             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7266                 n = PieceToNumber(piece);
7267                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7268                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7269                 boards[currentMove][n][BOARD_WIDTH-2]++;
7270             }
7271             boards[currentMove][fromY][fromX] = EmptySquare;
7272         }
7273         ClearHighlights();
7274         fromX = fromY = -1;
7275         DrawPosition(TRUE, boards[currentMove]);
7276         return;
7277     }
7278
7279     // off-board moves should not be highlighted
7280     if(x < 0 || y < 0) ClearHighlights();
7281
7282     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7283
7284     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7285         SetHighlights(fromX, fromY, toX, toY);
7286         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7287             // [HGM] super: promotion to captured piece selected from holdings
7288             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7289             promotionChoice = TRUE;
7290             // kludge follows to temporarily execute move on display, without promoting yet
7291             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7292             boards[currentMove][toY][toX] = p;
7293             DrawPosition(FALSE, boards[currentMove]);
7294             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7295             boards[currentMove][toY][toX] = q;
7296             DisplayMessage("Click in holdings to choose piece", "");
7297             return;
7298         }
7299         PromotionPopUp();
7300     } else {
7301         int oldMove = currentMove;
7302         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7303         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7304         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7305         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7306            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7307             DrawPosition(TRUE, boards[currentMove]);
7308         fromX = fromY = -1;
7309     }
7310     appData.animate = saveAnimate;
7311     if (appData.animate || appData.animateDragging) {
7312         /* Undo animation damage if needed */
7313         DrawPosition(FALSE, NULL);
7314     }
7315 }
7316
7317 int
7318 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7319 {   // front-end-free part taken out of PieceMenuPopup
7320     int whichMenu; int xSqr, ySqr;
7321
7322     if(seekGraphUp) { // [HGM] seekgraph
7323         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7324         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7325         return -2;
7326     }
7327
7328     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7329          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7330         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7331         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7332         if(action == Press)   {
7333             originalFlip = flipView;
7334             flipView = !flipView; // temporarily flip board to see game from partners perspective
7335             DrawPosition(TRUE, partnerBoard);
7336             DisplayMessage(partnerStatus, "");
7337             partnerUp = TRUE;
7338         } else if(action == Release) {
7339             flipView = originalFlip;
7340             DrawPosition(TRUE, boards[currentMove]);
7341             partnerUp = FALSE;
7342         }
7343         return -2;
7344     }
7345
7346     xSqr = EventToSquare(x, BOARD_WIDTH);
7347     ySqr = EventToSquare(y, BOARD_HEIGHT);
7348     if (action == Release) {
7349         if(pieceSweep != EmptySquare) {
7350             EditPositionMenuEvent(pieceSweep, toX, toY);
7351             pieceSweep = EmptySquare;
7352         } else UnLoadPV(); // [HGM] pv
7353     }
7354     if (action != Press) return -2; // return code to be ignored
7355     switch (gameMode) {
7356       case IcsExamining:
7357         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7358       case EditPosition:
7359         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7360         if (xSqr < 0 || ySqr < 0) return -1;
7361         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7362         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7363         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7364         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7365         NextPiece(0);
7366         return 2; // grab
7367       case IcsObserving:
7368         if(!appData.icsEngineAnalyze) return -1;
7369       case IcsPlayingWhite:
7370       case IcsPlayingBlack:
7371         if(!appData.zippyPlay) goto noZip;
7372       case AnalyzeMode:
7373       case AnalyzeFile:
7374       case MachinePlaysWhite:
7375       case MachinePlaysBlack:
7376       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7377         if (!appData.dropMenu) {
7378           LoadPV(x, y);
7379           return 2; // flag front-end to grab mouse events
7380         }
7381         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7382            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7383       case EditGame:
7384       noZip:
7385         if (xSqr < 0 || ySqr < 0) return -1;
7386         if (!appData.dropMenu || appData.testLegality &&
7387             gameInfo.variant != VariantBughouse &&
7388             gameInfo.variant != VariantCrazyhouse) return -1;
7389         whichMenu = 1; // drop menu
7390         break;
7391       default:
7392         return -1;
7393     }
7394
7395     if (((*fromX = xSqr) < 0) ||
7396         ((*fromY = ySqr) < 0)) {
7397         *fromX = *fromY = -1;
7398         return -1;
7399     }
7400     if (flipView)
7401       *fromX = BOARD_WIDTH - 1 - *fromX;
7402     else
7403       *fromY = BOARD_HEIGHT - 1 - *fromY;
7404
7405     return whichMenu;
7406 }
7407
7408 void
7409 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7410 {
7411 //    char * hint = lastHint;
7412     FrontEndProgramStats stats;
7413
7414     stats.which = cps == &first ? 0 : 1;
7415     stats.depth = cpstats->depth;
7416     stats.nodes = cpstats->nodes;
7417     stats.score = cpstats->score;
7418     stats.time = cpstats->time;
7419     stats.pv = cpstats->movelist;
7420     stats.hint = lastHint;
7421     stats.an_move_index = 0;
7422     stats.an_move_count = 0;
7423
7424     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7425         stats.hint = cpstats->move_name;
7426         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7427         stats.an_move_count = cpstats->nr_moves;
7428     }
7429
7430     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
7431
7432     SetProgramStats( &stats );
7433 }
7434
7435 void
7436 ClearEngineOutputPane (int which)
7437 {
7438     static FrontEndProgramStats dummyStats;
7439     dummyStats.which = which;
7440     dummyStats.pv = "#";
7441     SetProgramStats( &dummyStats );
7442 }
7443
7444 #define MAXPLAYERS 500
7445
7446 char *
7447 TourneyStandings (int display)
7448 {
7449     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7450     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7451     char result, *p, *names[MAXPLAYERS];
7452
7453     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7454         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7455     names[0] = p = strdup(appData.participants);
7456     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7457
7458     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7459
7460     while(result = appData.results[nr]) {
7461         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7462         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7463         wScore = bScore = 0;
7464         switch(result) {
7465           case '+': wScore = 2; break;
7466           case '-': bScore = 2; break;
7467           case '=': wScore = bScore = 1; break;
7468           case ' ':
7469           case '*': return strdup("busy"); // tourney not finished
7470         }
7471         score[w] += wScore;
7472         score[b] += bScore;
7473         games[w]++;
7474         games[b]++;
7475         nr++;
7476     }
7477     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7478     for(w=0; w<nPlayers; w++) {
7479         bScore = -1;
7480         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7481         ranking[w] = b; points[w] = bScore; score[b] = -2;
7482     }
7483     p = malloc(nPlayers*34+1);
7484     for(w=0; w<nPlayers && w<display; w++)
7485         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7486     free(names[0]);
7487     return p;
7488 }
7489
7490 void
7491 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7492 {       // count all piece types
7493         int p, f, r;
7494         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7495         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7496         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7497                 p = board[r][f];
7498                 pCnt[p]++;
7499                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7500                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7501                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7502                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7503                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7504                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7505         }
7506 }
7507
7508 int
7509 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7510 {
7511         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7512         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7513
7514         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7515         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7516         if(myPawns == 2 && nMine == 3) // KPP
7517             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7518         if(myPawns == 1 && nMine == 2) // KP
7519             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7520         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7521             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7522         if(myPawns) return FALSE;
7523         if(pCnt[WhiteRook+side])
7524             return pCnt[BlackRook-side] ||
7525                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7526                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7527                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7528         if(pCnt[WhiteCannon+side]) {
7529             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7530             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7531         }
7532         if(pCnt[WhiteKnight+side])
7533             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7534         return FALSE;
7535 }
7536
7537 int
7538 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7539 {
7540         VariantClass v = gameInfo.variant;
7541
7542         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7543         if(v == VariantShatranj) return TRUE; // always winnable through baring
7544         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7545         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7546
7547         if(v == VariantXiangqi) {
7548                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7549
7550                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7551                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7552                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7553                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7554                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7555                 if(stale) // we have at least one last-rank P plus perhaps C
7556                     return majors // KPKX
7557                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7558                 else // KCA*E*
7559                     return pCnt[WhiteFerz+side] // KCAK
7560                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7561                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7562                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7563
7564         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7565                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7566
7567                 if(nMine == 1) return FALSE; // bare King
7568                 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
7569                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7570                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7571                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7572                 if(pCnt[WhiteKnight+side])
7573                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7574                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7575                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7576                 if(nBishops)
7577                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7578                 if(pCnt[WhiteAlfil+side])
7579                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7580                 if(pCnt[WhiteWazir+side])
7581                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7582         }
7583
7584         return TRUE;
7585 }
7586
7587 int
7588 CompareWithRights (Board b1, Board b2)
7589 {
7590     int rights = 0;
7591     if(!CompareBoards(b1, b2)) return FALSE;
7592     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7593     /* compare castling rights */
7594     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7595            rights++; /* King lost rights, while rook still had them */
7596     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7597         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7598            rights++; /* but at least one rook lost them */
7599     }
7600     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7601            rights++;
7602     if( b1[CASTLING][5] != NoRights ) {
7603         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7604            rights++;
7605     }
7606     return rights == 0;
7607 }
7608
7609 int
7610 Adjudicate (ChessProgramState *cps)
7611 {       // [HGM] some adjudications useful with buggy engines
7612         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7613         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7614         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7615         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7616         int k, count = 0; static int bare = 1;
7617         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7618         Boolean canAdjudicate = !appData.icsActive;
7619
7620         // most tests only when we understand the game, i.e. legality-checking on
7621             if( appData.testLegality )
7622             {   /* [HGM] Some more adjudications for obstinate engines */
7623                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7624                 static int moveCount = 6;
7625                 ChessMove result;
7626                 char *reason = NULL;
7627
7628                 /* Count what is on board. */
7629                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7630
7631                 /* Some material-based adjudications that have to be made before stalemate test */
7632                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7633                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7634                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7635                      if(canAdjudicate && appData.checkMates) {
7636                          if(engineOpponent)
7637                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7638                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7639                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7640                          return 1;
7641                      }
7642                 }
7643
7644                 /* Bare King in Shatranj (loses) or Losers (wins) */
7645                 if( nrW == 1 || nrB == 1) {
7646                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7647                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7648                      if(canAdjudicate && appData.checkMates) {
7649                          if(engineOpponent)
7650                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7651                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7652                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7653                          return 1;
7654                      }
7655                   } else
7656                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7657                   {    /* bare King */
7658                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7659                         if(canAdjudicate && appData.checkMates) {
7660                             /* but only adjudicate if adjudication enabled */
7661                             if(engineOpponent)
7662                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7663                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7664                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7665                             return 1;
7666                         }
7667                   }
7668                 } else bare = 1;
7669
7670
7671             // don't wait for engine to announce game end if we can judge ourselves
7672             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7673               case MT_CHECK:
7674                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7675                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7676                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7677                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7678                             checkCnt++;
7679                         if(checkCnt >= 2) {
7680                             reason = "Xboard adjudication: 3rd check";
7681                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7682                             break;
7683                         }
7684                     }
7685                 }
7686               case MT_NONE:
7687               default:
7688                 break;
7689               case MT_STALEMATE:
7690               case MT_STAINMATE:
7691                 reason = "Xboard adjudication: Stalemate";
7692                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7693                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7694                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7695                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7696                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7697                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7698                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7699                                                                         EP_CHECKMATE : EP_WINS);
7700                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7701                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7702                 }
7703                 break;
7704               case MT_CHECKMATE:
7705                 reason = "Xboard adjudication: Checkmate";
7706                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7707                 break;
7708             }
7709
7710                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7711                     case EP_STALEMATE:
7712                         result = GameIsDrawn; break;
7713                     case EP_CHECKMATE:
7714                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7715                     case EP_WINS:
7716                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7717                     default:
7718                         result = EndOfFile;
7719                 }
7720                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7721                     if(engineOpponent)
7722                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7723                     GameEnds( result, reason, GE_XBOARD );
7724                     return 1;
7725                 }
7726
7727                 /* Next absolutely insufficient mating material. */
7728                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7729                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7730                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7731
7732                      /* always flag draws, for judging claims */
7733                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7734
7735                      if(canAdjudicate && appData.materialDraws) {
7736                          /* but only adjudicate them if adjudication enabled */
7737                          if(engineOpponent) {
7738                            SendToProgram("force\n", engineOpponent); // suppress reply
7739                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7740                          }
7741                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7742                          return 1;
7743                      }
7744                 }
7745
7746                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7747                 if(gameInfo.variant == VariantXiangqi ?
7748                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7749                  : nrW + nrB == 4 &&
7750                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7751                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7752                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7753                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7754                    ) ) {
7755                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7756                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7757                           if(engineOpponent) {
7758                             SendToProgram("force\n", engineOpponent); // suppress reply
7759                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7760                           }
7761                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7762                           return 1;
7763                      }
7764                 } else moveCount = 6;
7765             }
7766
7767         // Repetition draws and 50-move rule can be applied independently of legality testing
7768
7769                 /* Check for rep-draws */
7770                 count = 0;
7771                 for(k = forwardMostMove-2;
7772                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7773                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7774                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7775                     k-=2)
7776                 {   int rights=0;
7777                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7778                         /* compare castling rights */
7779                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7780                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7781                                 rights++; /* King lost rights, while rook still had them */
7782                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7783                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7784                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7785                                    rights++; /* but at least one rook lost them */
7786                         }
7787                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7788                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7789                                 rights++;
7790                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7791                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7792                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7793                                    rights++;
7794                         }
7795                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7796                             && appData.drawRepeats > 1) {
7797                              /* adjudicate after user-specified nr of repeats */
7798                              int result = GameIsDrawn;
7799                              char *details = "XBoard adjudication: repetition draw";
7800                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7801                                 // [HGM] xiangqi: check for forbidden perpetuals
7802                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7803                                 for(m=forwardMostMove; m>k; m-=2) {
7804                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7805                                         ourPerpetual = 0; // the current mover did not always check
7806                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7807                                         hisPerpetual = 0; // the opponent did not always check
7808                                 }
7809                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7810                                                                         ourPerpetual, hisPerpetual);
7811                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7812                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7813                                     details = "Xboard adjudication: perpetual checking";
7814                                 } else
7815                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7816                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7817                                 } else
7818                                 // Now check for perpetual chases
7819                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7820                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7821                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7822                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7823                                         static char resdet[MSG_SIZ];
7824                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7825                                         details = resdet;
7826                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7827                                     } else
7828                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7829                                         break; // Abort repetition-checking loop.
7830                                 }
7831                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7832                              }
7833                              if(engineOpponent) {
7834                                SendToProgram("force\n", engineOpponent); // suppress reply
7835                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7836                              }
7837                              GameEnds( result, details, GE_XBOARD );
7838                              return 1;
7839                         }
7840                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7841                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7842                     }
7843                 }
7844
7845                 /* Now we test for 50-move draws. Determine ply count */
7846                 count = forwardMostMove;
7847                 /* look for last irreversble move */
7848                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7849                     count--;
7850                 /* if we hit starting position, add initial plies */
7851                 if( count == backwardMostMove )
7852                     count -= initialRulePlies;
7853                 count = forwardMostMove - count;
7854                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7855                         // adjust reversible move counter for checks in Xiangqi
7856                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7857                         if(i < backwardMostMove) i = backwardMostMove;
7858                         while(i <= forwardMostMove) {
7859                                 lastCheck = inCheck; // check evasion does not count
7860                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7861                                 if(inCheck || lastCheck) count--; // check does not count
7862                                 i++;
7863                         }
7864                 }
7865                 if( count >= 100)
7866                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7867                          /* this is used to judge if draw claims are legal */
7868                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7869                          if(engineOpponent) {
7870                            SendToProgram("force\n", engineOpponent); // suppress reply
7871                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7872                          }
7873                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7874                          return 1;
7875                 }
7876
7877                 /* if draw offer is pending, treat it as a draw claim
7878                  * when draw condition present, to allow engines a way to
7879                  * claim draws before making their move to avoid a race
7880                  * condition occurring after their move
7881                  */
7882                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7883                          char *p = NULL;
7884                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7885                              p = "Draw claim: 50-move rule";
7886                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7887                              p = "Draw claim: 3-fold repetition";
7888                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7889                              p = "Draw claim: insufficient mating material";
7890                          if( p != NULL && canAdjudicate) {
7891                              if(engineOpponent) {
7892                                SendToProgram("force\n", engineOpponent); // suppress reply
7893                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7894                              }
7895                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7896                              return 1;
7897                          }
7898                 }
7899
7900                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7901                     if(engineOpponent) {
7902                       SendToProgram("force\n", engineOpponent); // suppress reply
7903                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7904                     }
7905                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7906                     return 1;
7907                 }
7908         return 0;
7909 }
7910
7911 char *
7912 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7913 {   // [HGM] book: this routine intercepts moves to simulate book replies
7914     char *bookHit = NULL;
7915
7916     //first determine if the incoming move brings opponent into his book
7917     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7918         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7919     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7920     if(bookHit != NULL && !cps->bookSuspend) {
7921         // make sure opponent is not going to reply after receiving move to book position
7922         SendToProgram("force\n", cps);
7923         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7924     }
7925     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7926     // now arrange restart after book miss
7927     if(bookHit) {
7928         // after a book hit we never send 'go', and the code after the call to this routine
7929         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7930         char buf[MSG_SIZ], *move = bookHit;
7931         if(cps->useSAN) {
7932             int fromX, fromY, toX, toY;
7933             char promoChar;
7934             ChessMove moveType;
7935             move = buf + 30;
7936             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7937                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7938                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7939                                     PosFlags(forwardMostMove),
7940                                     fromY, fromX, toY, toX, promoChar, move);
7941             } else {
7942                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7943                 bookHit = NULL;
7944             }
7945         }
7946         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7947         SendToProgram(buf, cps);
7948         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7949     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7950         SendToProgram("go\n", cps);
7951         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7952     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7953         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7954             SendToProgram("go\n", cps);
7955         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7956     }
7957     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7958 }
7959
7960 int
7961 LoadError (char *errmess, ChessProgramState *cps)
7962 {   // unloads engine and switches back to -ncp mode if it was first
7963     if(cps->initDone) return FALSE;
7964     cps->isr = NULL; // this should suppress further error popups from breaking pipes
7965     DestroyChildProcess(cps->pr, 9 ); // just to be sure
7966     cps->pr = NoProc; 
7967     if(cps == &first) {
7968         appData.noChessProgram = TRUE;
7969         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
7970         gameMode = BeginningOfGame; ModeHighlight();
7971         SetNCPMode();
7972     }
7973     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
7974     DisplayMessage("", ""); // erase waiting message
7975     if(errmess) DisplayError(errmess, 0); // announce reason, if given
7976     return TRUE;
7977 }
7978
7979 char *savedMessage;
7980 ChessProgramState *savedState;
7981 void
7982 DeferredBookMove (void)
7983 {
7984         if(savedState->lastPing != savedState->lastPong)
7985                     ScheduleDelayedEvent(DeferredBookMove, 10);
7986         else
7987         HandleMachineMove(savedMessage, savedState);
7988 }
7989
7990 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7991
7992 void
7993 HandleMachineMove (char *message, ChessProgramState *cps)
7994 {
7995     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7996     char realname[MSG_SIZ];
7997     int fromX, fromY, toX, toY;
7998     ChessMove moveType;
7999     char promoChar;
8000     char *p, *pv=buf1;
8001     int machineWhite, oldError;
8002     char *bookHit;
8003
8004     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8005         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8006         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8007             DisplayError(_("Invalid pairing from pairing engine"), 0);
8008             return;
8009         }
8010         pairingReceived = 1;
8011         NextMatchGame();
8012         return; // Skim the pairing messages here.
8013     }
8014
8015     oldError = cps->userError; cps->userError = 0;
8016
8017 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8018     /*
8019      * Kludge to ignore BEL characters
8020      */
8021     while (*message == '\007') message++;
8022
8023     /*
8024      * [HGM] engine debug message: ignore lines starting with '#' character
8025      */
8026     if(cps->debug && *message == '#') return;
8027
8028     /*
8029      * Look for book output
8030      */
8031     if (cps == &first && bookRequested) {
8032         if (message[0] == '\t' || message[0] == ' ') {
8033             /* Part of the book output is here; append it */
8034             strcat(bookOutput, message);
8035             strcat(bookOutput, "  \n");
8036             return;
8037         } else if (bookOutput[0] != NULLCHAR) {
8038             /* All of book output has arrived; display it */
8039             char *p = bookOutput;
8040             while (*p != NULLCHAR) {
8041                 if (*p == '\t') *p = ' ';
8042                 p++;
8043             }
8044             DisplayInformation(bookOutput);
8045             bookRequested = FALSE;
8046             /* Fall through to parse the current output */
8047         }
8048     }
8049
8050     /*
8051      * Look for machine move.
8052      */
8053     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8054         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8055     {
8056         /* This method is only useful on engines that support ping */
8057         if (cps->lastPing != cps->lastPong) {
8058           if (gameMode == BeginningOfGame) {
8059             /* Extra move from before last new; ignore */
8060             if (appData.debugMode) {
8061                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8062             }
8063           } else {
8064             if (appData.debugMode) {
8065                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8066                         cps->which, gameMode);
8067             }
8068
8069             SendToProgram("undo\n", cps);
8070           }
8071           return;
8072         }
8073
8074         switch (gameMode) {
8075           case BeginningOfGame:
8076             /* Extra move from before last reset; ignore */
8077             if (appData.debugMode) {
8078                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8079             }
8080             return;
8081
8082           case EndOfGame:
8083           case IcsIdle:
8084           default:
8085             /* Extra move after we tried to stop.  The mode test is
8086                not a reliable way of detecting this problem, but it's
8087                the best we can do on engines that don't support ping.
8088             */
8089             if (appData.debugMode) {
8090                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8091                         cps->which, gameMode);
8092             }
8093             SendToProgram("undo\n", cps);
8094             return;
8095
8096           case MachinePlaysWhite:
8097           case IcsPlayingWhite:
8098             machineWhite = TRUE;
8099             break;
8100
8101           case MachinePlaysBlack:
8102           case IcsPlayingBlack:
8103             machineWhite = FALSE;
8104             break;
8105
8106           case TwoMachinesPlay:
8107             machineWhite = (cps->twoMachinesColor[0] == 'w');
8108             break;
8109         }
8110         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8111             if (appData.debugMode) {
8112                 fprintf(debugFP,
8113                         "Ignoring move out of turn by %s, gameMode %d"
8114                         ", forwardMost %d\n",
8115                         cps->which, gameMode, forwardMostMove);
8116             }
8117             return;
8118         }
8119
8120         if(cps->alphaRank) AlphaRank(machineMove, 4);
8121         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8122                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8123             /* Machine move could not be parsed; ignore it. */
8124           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8125                     machineMove, _(cps->which));
8126             DisplayError(buf1, 0);
8127             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8128                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8129             if (gameMode == TwoMachinesPlay) {
8130               GameEnds(machineWhite ? BlackWins : WhiteWins,
8131                        buf1, GE_XBOARD);
8132             }
8133             return;
8134         }
8135
8136         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8137         /* So we have to redo legality test with true e.p. status here,  */
8138         /* to make sure an illegal e.p. capture does not slip through,   */
8139         /* to cause a forfeit on a justified illegal-move complaint      */
8140         /* of the opponent.                                              */
8141         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8142            ChessMove moveType;
8143            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8144                              fromY, fromX, toY, toX, promoChar);
8145             if(moveType == IllegalMove) {
8146               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8147                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8148                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8149                            buf1, GE_XBOARD);
8150                 return;
8151            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8152            /* [HGM] Kludge to handle engines that send FRC-style castling
8153               when they shouldn't (like TSCP-Gothic) */
8154            switch(moveType) {
8155              case WhiteASideCastleFR:
8156              case BlackASideCastleFR:
8157                toX+=2;
8158                currentMoveString[2]++;
8159                break;
8160              case WhiteHSideCastleFR:
8161              case BlackHSideCastleFR:
8162                toX--;
8163                currentMoveString[2]--;
8164                break;
8165              default: ; // nothing to do, but suppresses warning of pedantic compilers
8166            }
8167         }
8168         hintRequested = FALSE;
8169         lastHint[0] = NULLCHAR;
8170         bookRequested = FALSE;
8171         /* Program may be pondering now */
8172         cps->maybeThinking = TRUE;
8173         if (cps->sendTime == 2) cps->sendTime = 1;
8174         if (cps->offeredDraw) cps->offeredDraw--;
8175
8176         /* [AS] Save move info*/
8177         pvInfoList[ forwardMostMove ].score = programStats.score;
8178         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8179         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8180
8181         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8182
8183         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8184         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8185             int count = 0;
8186
8187             while( count < adjudicateLossPlies ) {
8188                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8189
8190                 if( count & 1 ) {
8191                     score = -score; /* Flip score for winning side */
8192                 }
8193
8194                 if( score > adjudicateLossThreshold ) {
8195                     break;
8196                 }
8197
8198                 count++;
8199             }
8200
8201             if( count >= adjudicateLossPlies ) {
8202                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8203
8204                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8205                     "Xboard adjudication",
8206                     GE_XBOARD );
8207
8208                 return;
8209             }
8210         }
8211
8212         if(Adjudicate(cps)) {
8213             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8214             return; // [HGM] adjudicate: for all automatic game ends
8215         }
8216
8217 #if ZIPPY
8218         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8219             first.initDone) {
8220           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8221                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8222                 SendToICS("draw ");
8223                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8224           }
8225           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8226           ics_user_moved = 1;
8227           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8228                 char buf[3*MSG_SIZ];
8229
8230                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8231                         programStats.score / 100.,
8232                         programStats.depth,
8233                         programStats.time / 100.,
8234                         (unsigned int)programStats.nodes,
8235                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8236                         programStats.movelist);
8237                 SendToICS(buf);
8238 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8239           }
8240         }
8241 #endif
8242
8243         /* [AS] Clear stats for next move */
8244         ClearProgramStats();
8245         thinkOutput[0] = NULLCHAR;
8246         hiddenThinkOutputState = 0;
8247
8248         bookHit = NULL;
8249         if (gameMode == TwoMachinesPlay) {
8250             /* [HGM] relaying draw offers moved to after reception of move */
8251             /* and interpreting offer as claim if it brings draw condition */
8252             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8253                 SendToProgram("draw\n", cps->other);
8254             }
8255             if (cps->other->sendTime) {
8256                 SendTimeRemaining(cps->other,
8257                                   cps->other->twoMachinesColor[0] == 'w');
8258             }
8259             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8260             if (firstMove && !bookHit) {
8261                 firstMove = FALSE;
8262                 if (cps->other->useColors) {
8263                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8264                 }
8265                 SendToProgram("go\n", cps->other);
8266             }
8267             cps->other->maybeThinking = TRUE;
8268         }
8269
8270         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8271
8272         if (!pausing && appData.ringBellAfterMoves) {
8273             RingBell();
8274         }
8275
8276         /*
8277          * Reenable menu items that were disabled while
8278          * machine was thinking
8279          */
8280         if (gameMode != TwoMachinesPlay)
8281             SetUserThinkingEnables();
8282
8283         // [HGM] book: after book hit opponent has received move and is now in force mode
8284         // force the book reply into it, and then fake that it outputted this move by jumping
8285         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8286         if(bookHit) {
8287                 static char bookMove[MSG_SIZ]; // a bit generous?
8288
8289                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8290                 strcat(bookMove, bookHit);
8291                 message = bookMove;
8292                 cps = cps->other;
8293                 programStats.nodes = programStats.depth = programStats.time =
8294                 programStats.score = programStats.got_only_move = 0;
8295                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8296
8297                 if(cps->lastPing != cps->lastPong) {
8298                     savedMessage = message; // args for deferred call
8299                     savedState = cps;
8300                     ScheduleDelayedEvent(DeferredBookMove, 10);
8301                     return;
8302                 }
8303                 goto FakeBookMove;
8304         }
8305
8306         return;
8307     }
8308
8309     /* Set special modes for chess engines.  Later something general
8310      *  could be added here; for now there is just one kludge feature,
8311      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8312      *  when "xboard" is given as an interactive command.
8313      */
8314     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8315         cps->useSigint = FALSE;
8316         cps->useSigterm = FALSE;
8317     }
8318     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8319       ParseFeatures(message+8, cps);
8320       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8321     }
8322
8323     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8324                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8325       int dummy, s=6; char buf[MSG_SIZ];
8326       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8327       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8328       if(startedFromSetupPosition) return;
8329       ParseFEN(boards[0], &dummy, message+s);
8330       DrawPosition(TRUE, boards[0]);
8331       startedFromSetupPosition = TRUE;
8332       return;
8333     }
8334     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8335      * want this, I was asked to put it in, and obliged.
8336      */
8337     if (!strncmp(message, "setboard ", 9)) {
8338         Board initial_position;
8339
8340         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8341
8342         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8343             DisplayError(_("Bad FEN received from engine"), 0);
8344             return ;
8345         } else {
8346            Reset(TRUE, FALSE);
8347            CopyBoard(boards[0], initial_position);
8348            initialRulePlies = FENrulePlies;
8349            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8350            else gameMode = MachinePlaysBlack;
8351            DrawPosition(FALSE, boards[currentMove]);
8352         }
8353         return;
8354     }
8355
8356     /*
8357      * Look for communication commands
8358      */
8359     if (!strncmp(message, "telluser ", 9)) {
8360         if(message[9] == '\\' && message[10] == '\\')
8361             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8362         PlayTellSound();
8363         DisplayNote(message + 9);
8364         return;
8365     }
8366     if (!strncmp(message, "tellusererror ", 14)) {
8367         cps->userError = 1;
8368         if(message[14] == '\\' && message[15] == '\\')
8369             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8370         PlayTellSound();
8371         DisplayError(message + 14, 0);
8372         return;
8373     }
8374     if (!strncmp(message, "tellopponent ", 13)) {
8375       if (appData.icsActive) {
8376         if (loggedOn) {
8377           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8378           SendToICS(buf1);
8379         }
8380       } else {
8381         DisplayNote(message + 13);
8382       }
8383       return;
8384     }
8385     if (!strncmp(message, "tellothers ", 11)) {
8386       if (appData.icsActive) {
8387         if (loggedOn) {
8388           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8389           SendToICS(buf1);
8390         }
8391       }
8392       return;
8393     }
8394     if (!strncmp(message, "tellall ", 8)) {
8395       if (appData.icsActive) {
8396         if (loggedOn) {
8397           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8398           SendToICS(buf1);
8399         }
8400       } else {
8401         DisplayNote(message + 8);
8402       }
8403       return;
8404     }
8405     if (strncmp(message, "warning", 7) == 0) {
8406         /* Undocumented feature, use tellusererror in new code */
8407         DisplayError(message, 0);
8408         return;
8409     }
8410     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8411         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8412         strcat(realname, " query");
8413         AskQuestion(realname, buf2, buf1, cps->pr);
8414         return;
8415     }
8416     /* Commands from the engine directly to ICS.  We don't allow these to be
8417      *  sent until we are logged on. Crafty kibitzes have been known to
8418      *  interfere with the login process.
8419      */
8420     if (loggedOn) {
8421         if (!strncmp(message, "tellics ", 8)) {
8422             SendToICS(message + 8);
8423             SendToICS("\n");
8424             return;
8425         }
8426         if (!strncmp(message, "tellicsnoalias ", 15)) {
8427             SendToICS(ics_prefix);
8428             SendToICS(message + 15);
8429             SendToICS("\n");
8430             return;
8431         }
8432         /* The following are for backward compatibility only */
8433         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8434             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8435             SendToICS(ics_prefix);
8436             SendToICS(message);
8437             SendToICS("\n");
8438             return;
8439         }
8440     }
8441     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8442         return;
8443     }
8444     /*
8445      * If the move is illegal, cancel it and redraw the board.
8446      * Also deal with other error cases.  Matching is rather loose
8447      * here to accommodate engines written before the spec.
8448      */
8449     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8450         strncmp(message, "Error", 5) == 0) {
8451         if (StrStr(message, "name") ||
8452             StrStr(message, "rating") || StrStr(message, "?") ||
8453             StrStr(message, "result") || StrStr(message, "board") ||
8454             StrStr(message, "bk") || StrStr(message, "computer") ||
8455             StrStr(message, "variant") || StrStr(message, "hint") ||
8456             StrStr(message, "random") || StrStr(message, "depth") ||
8457             StrStr(message, "accepted")) {
8458             return;
8459         }
8460         if (StrStr(message, "protover")) {
8461           /* Program is responding to input, so it's apparently done
8462              initializing, and this error message indicates it is
8463              protocol version 1.  So we don't need to wait any longer
8464              for it to initialize and send feature commands. */
8465           FeatureDone(cps, 1);
8466           cps->protocolVersion = 1;
8467           return;
8468         }
8469         cps->maybeThinking = FALSE;
8470
8471         if (StrStr(message, "draw")) {
8472             /* Program doesn't have "draw" command */
8473             cps->sendDrawOffers = 0;
8474             return;
8475         }
8476         if (cps->sendTime != 1 &&
8477             (StrStr(message, "time") || StrStr(message, "otim"))) {
8478           /* Program apparently doesn't have "time" or "otim" command */
8479           cps->sendTime = 0;
8480           return;
8481         }
8482         if (StrStr(message, "analyze")) {
8483             cps->analysisSupport = FALSE;
8484             cps->analyzing = FALSE;
8485 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8486             EditGameEvent(); // [HGM] try to preserve loaded game
8487             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8488             DisplayError(buf2, 0);
8489             return;
8490         }
8491         if (StrStr(message, "(no matching move)st")) {
8492           /* Special kludge for GNU Chess 4 only */
8493           cps->stKludge = TRUE;
8494           SendTimeControl(cps, movesPerSession, timeControl,
8495                           timeIncrement, appData.searchDepth,
8496                           searchTime);
8497           return;
8498         }
8499         if (StrStr(message, "(no matching move)sd")) {
8500           /* Special kludge for GNU Chess 4 only */
8501           cps->sdKludge = TRUE;
8502           SendTimeControl(cps, movesPerSession, timeControl,
8503                           timeIncrement, appData.searchDepth,
8504                           searchTime);
8505           return;
8506         }
8507         if (!StrStr(message, "llegal")) {
8508             return;
8509         }
8510         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8511             gameMode == IcsIdle) return;
8512         if (forwardMostMove <= backwardMostMove) return;
8513         if (pausing) PauseEvent();
8514       if(appData.forceIllegal) {
8515             // [HGM] illegal: machine refused move; force position after move into it
8516           SendToProgram("force\n", cps);
8517           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8518                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8519                 // when black is to move, while there might be nothing on a2 or black
8520                 // might already have the move. So send the board as if white has the move.
8521                 // But first we must change the stm of the engine, as it refused the last move
8522                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8523                 if(WhiteOnMove(forwardMostMove)) {
8524                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8525                     SendBoard(cps, forwardMostMove); // kludgeless board
8526                 } else {
8527                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8528                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8529                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8530                 }
8531           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8532             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8533                  gameMode == TwoMachinesPlay)
8534               SendToProgram("go\n", cps);
8535             return;
8536       } else
8537         if (gameMode == PlayFromGameFile) {
8538             /* Stop reading this game file */
8539             gameMode = EditGame;
8540             ModeHighlight();
8541         }
8542         /* [HGM] illegal-move claim should forfeit game when Xboard */
8543         /* only passes fully legal moves                            */
8544         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8545             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8546                                 "False illegal-move claim", GE_XBOARD );
8547             return; // do not take back move we tested as valid
8548         }
8549         currentMove = forwardMostMove-1;
8550         DisplayMove(currentMove-1); /* before DisplayMoveError */
8551         SwitchClocks(forwardMostMove-1); // [HGM] race
8552         DisplayBothClocks();
8553         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8554                 parseList[currentMove], _(cps->which));
8555         DisplayMoveError(buf1);
8556         DrawPosition(FALSE, boards[currentMove]);
8557
8558         SetUserThinkingEnables();
8559         return;
8560     }
8561     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8562         /* Program has a broken "time" command that
8563            outputs a string not ending in newline.
8564            Don't use it. */
8565         cps->sendTime = 0;
8566     }
8567
8568     /*
8569      * If chess program startup fails, exit with an error message.
8570      * Attempts to recover here are futile. [HGM] Well, we try anyway
8571      */
8572     if ((StrStr(message, "unknown host") != NULL)
8573         || (StrStr(message, "No remote directory") != NULL)
8574         || (StrStr(message, "not found") != NULL)
8575         || (StrStr(message, "No such file") != NULL)
8576         || (StrStr(message, "can't alloc") != NULL)
8577         || (StrStr(message, "Permission denied") != NULL)) {
8578
8579         cps->maybeThinking = FALSE;
8580         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8581                 _(cps->which), cps->program, cps->host, message);
8582         RemoveInputSource(cps->isr);
8583         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8584             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8585             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8586         }
8587         return;
8588     }
8589
8590     /*
8591      * Look for hint output
8592      */
8593     if (sscanf(message, "Hint: %s", buf1) == 1) {
8594         if (cps == &first && hintRequested) {
8595             hintRequested = FALSE;
8596             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8597                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8598                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8599                                     PosFlags(forwardMostMove),
8600                                     fromY, fromX, toY, toX, promoChar, buf1);
8601                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8602                 DisplayInformation(buf2);
8603             } else {
8604                 /* Hint move could not be parsed!? */
8605               snprintf(buf2, sizeof(buf2),
8606                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8607                         buf1, _(cps->which));
8608                 DisplayError(buf2, 0);
8609             }
8610         } else {
8611           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8612         }
8613         return;
8614     }
8615
8616     /*
8617      * Ignore other messages if game is not in progress
8618      */
8619     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8620         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8621
8622     /*
8623      * look for win, lose, draw, or draw offer
8624      */
8625     if (strncmp(message, "1-0", 3) == 0) {
8626         char *p, *q, *r = "";
8627         p = strchr(message, '{');
8628         if (p) {
8629             q = strchr(p, '}');
8630             if (q) {
8631                 *q = NULLCHAR;
8632                 r = p + 1;
8633             }
8634         }
8635         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8636         return;
8637     } else if (strncmp(message, "0-1", 3) == 0) {
8638         char *p, *q, *r = "";
8639         p = strchr(message, '{');
8640         if (p) {
8641             q = strchr(p, '}');
8642             if (q) {
8643                 *q = NULLCHAR;
8644                 r = p + 1;
8645             }
8646         }
8647         /* Kludge for Arasan 4.1 bug */
8648         if (strcmp(r, "Black resigns") == 0) {
8649             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8650             return;
8651         }
8652         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8653         return;
8654     } else if (strncmp(message, "1/2", 3) == 0) {
8655         char *p, *q, *r = "";
8656         p = strchr(message, '{');
8657         if (p) {
8658             q = strchr(p, '}');
8659             if (q) {
8660                 *q = NULLCHAR;
8661                 r = p + 1;
8662             }
8663         }
8664
8665         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8666         return;
8667
8668     } else if (strncmp(message, "White resign", 12) == 0) {
8669         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8670         return;
8671     } else if (strncmp(message, "Black resign", 12) == 0) {
8672         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8673         return;
8674     } else if (strncmp(message, "White matches", 13) == 0 ||
8675                strncmp(message, "Black matches", 13) == 0   ) {
8676         /* [HGM] ignore GNUShogi noises */
8677         return;
8678     } else if (strncmp(message, "White", 5) == 0 &&
8679                message[5] != '(' &&
8680                StrStr(message, "Black") == NULL) {
8681         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8682         return;
8683     } else if (strncmp(message, "Black", 5) == 0 &&
8684                message[5] != '(') {
8685         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8686         return;
8687     } else if (strcmp(message, "resign") == 0 ||
8688                strcmp(message, "computer resigns") == 0) {
8689         switch (gameMode) {
8690           case MachinePlaysBlack:
8691           case IcsPlayingBlack:
8692             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8693             break;
8694           case MachinePlaysWhite:
8695           case IcsPlayingWhite:
8696             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8697             break;
8698           case TwoMachinesPlay:
8699             if (cps->twoMachinesColor[0] == 'w')
8700               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8701             else
8702               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8703             break;
8704           default:
8705             /* can't happen */
8706             break;
8707         }
8708         return;
8709     } else if (strncmp(message, "opponent mates", 14) == 0) {
8710         switch (gameMode) {
8711           case MachinePlaysBlack:
8712           case IcsPlayingBlack:
8713             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8714             break;
8715           case MachinePlaysWhite:
8716           case IcsPlayingWhite:
8717             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8718             break;
8719           case TwoMachinesPlay:
8720             if (cps->twoMachinesColor[0] == 'w')
8721               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8722             else
8723               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8724             break;
8725           default:
8726             /* can't happen */
8727             break;
8728         }
8729         return;
8730     } else if (strncmp(message, "computer mates", 14) == 0) {
8731         switch (gameMode) {
8732           case MachinePlaysBlack:
8733           case IcsPlayingBlack:
8734             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8735             break;
8736           case MachinePlaysWhite:
8737           case IcsPlayingWhite:
8738             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8739             break;
8740           case TwoMachinesPlay:
8741             if (cps->twoMachinesColor[0] == 'w')
8742               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8743             else
8744               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8745             break;
8746           default:
8747             /* can't happen */
8748             break;
8749         }
8750         return;
8751     } else if (strncmp(message, "checkmate", 9) == 0) {
8752         if (WhiteOnMove(forwardMostMove)) {
8753             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8754         } else {
8755             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8756         }
8757         return;
8758     } else if (strstr(message, "Draw") != NULL ||
8759                strstr(message, "game is a draw") != NULL) {
8760         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8761         return;
8762     } else if (strstr(message, "offer") != NULL &&
8763                strstr(message, "draw") != NULL) {
8764 #if ZIPPY
8765         if (appData.zippyPlay && first.initDone) {
8766             /* Relay offer to ICS */
8767             SendToICS(ics_prefix);
8768             SendToICS("draw\n");
8769         }
8770 #endif
8771         cps->offeredDraw = 2; /* valid until this engine moves twice */
8772         if (gameMode == TwoMachinesPlay) {
8773             if (cps->other->offeredDraw) {
8774                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8775             /* [HGM] in two-machine mode we delay relaying draw offer      */
8776             /* until after we also have move, to see if it is really claim */
8777             }
8778         } else if (gameMode == MachinePlaysWhite ||
8779                    gameMode == MachinePlaysBlack) {
8780           if (userOfferedDraw) {
8781             DisplayInformation(_("Machine accepts your draw offer"));
8782             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8783           } else {
8784             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8785           }
8786         }
8787     }
8788
8789
8790     /*
8791      * Look for thinking output
8792      */
8793     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8794           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8795                                 ) {
8796         int plylev, mvleft, mvtot, curscore, time;
8797         char mvname[MOVE_LEN];
8798         u64 nodes; // [DM]
8799         char plyext;
8800         int ignore = FALSE;
8801         int prefixHint = FALSE;
8802         mvname[0] = NULLCHAR;
8803
8804         switch (gameMode) {
8805           case MachinePlaysBlack:
8806           case IcsPlayingBlack:
8807             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8808             break;
8809           case MachinePlaysWhite:
8810           case IcsPlayingWhite:
8811             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8812             break;
8813           case AnalyzeMode:
8814           case AnalyzeFile:
8815             break;
8816           case IcsObserving: /* [DM] icsEngineAnalyze */
8817             if (!appData.icsEngineAnalyze) ignore = TRUE;
8818             break;
8819           case TwoMachinesPlay:
8820             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8821                 ignore = TRUE;
8822             }
8823             break;
8824           default:
8825             ignore = TRUE;
8826             break;
8827         }
8828
8829         if (!ignore) {
8830             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8831             buf1[0] = NULLCHAR;
8832             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8833                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8834
8835                 if (plyext != ' ' && plyext != '\t') {
8836                     time *= 100;
8837                 }
8838
8839                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8840                 if( cps->scoreIsAbsolute &&
8841                     ( gameMode == MachinePlaysBlack ||
8842                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8843                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8844                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8845                      !WhiteOnMove(currentMove)
8846                     ) )
8847                 {
8848                     curscore = -curscore;
8849                 }
8850
8851                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8852
8853                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8854                         char buf[MSG_SIZ];
8855                         FILE *f;
8856                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8857                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8858                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8859                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8860                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8861                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8862                                 fclose(f);
8863                         } else DisplayError(_("failed writing PV"), 0);
8864                 }
8865
8866                 tempStats.depth = plylev;
8867                 tempStats.nodes = nodes;
8868                 tempStats.time = time;
8869                 tempStats.score = curscore;
8870                 tempStats.got_only_move = 0;
8871
8872                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8873                         int ticklen;
8874
8875                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8876                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8877                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8878                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8879                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8880                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8881                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8882                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8883                 }
8884
8885                 /* Buffer overflow protection */
8886                 if (pv[0] != NULLCHAR) {
8887                     if (strlen(pv) >= sizeof(tempStats.movelist)
8888                         && appData.debugMode) {
8889                         fprintf(debugFP,
8890                                 "PV is too long; using the first %u bytes.\n",
8891                                 (unsigned) sizeof(tempStats.movelist) - 1);
8892                     }
8893
8894                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8895                 } else {
8896                     sprintf(tempStats.movelist, " no PV\n");
8897                 }
8898
8899                 if (tempStats.seen_stat) {
8900                     tempStats.ok_to_send = 1;
8901                 }
8902
8903                 if (strchr(tempStats.movelist, '(') != NULL) {
8904                     tempStats.line_is_book = 1;
8905                     tempStats.nr_moves = 0;
8906                     tempStats.moves_left = 0;
8907                 } else {
8908                     tempStats.line_is_book = 0;
8909                 }
8910
8911                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8912                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8913
8914                 SendProgramStatsToFrontend( cps, &tempStats );
8915
8916                 /*
8917                     [AS] Protect the thinkOutput buffer from overflow... this
8918                     is only useful if buf1 hasn't overflowed first!
8919                 */
8920                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8921                          plylev,
8922                          (gameMode == TwoMachinesPlay ?
8923                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8924                          ((double) curscore) / 100.0,
8925                          prefixHint ? lastHint : "",
8926                          prefixHint ? " " : "" );
8927
8928                 if( buf1[0] != NULLCHAR ) {
8929                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8930
8931                     if( strlen(pv) > max_len ) {
8932                         if( appData.debugMode) {
8933                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8934                         }
8935                         pv[max_len+1] = '\0';
8936                     }
8937
8938                     strcat( thinkOutput, pv);
8939                 }
8940
8941                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8942                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8943                     DisplayMove(currentMove - 1);
8944                 }
8945                 return;
8946
8947             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8948                 /* crafty (9.25+) says "(only move) <move>"
8949                  * if there is only 1 legal move
8950                  */
8951                 sscanf(p, "(only move) %s", buf1);
8952                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8953                 sprintf(programStats.movelist, "%s (only move)", buf1);
8954                 programStats.depth = 1;
8955                 programStats.nr_moves = 1;
8956                 programStats.moves_left = 1;
8957                 programStats.nodes = 1;
8958                 programStats.time = 1;
8959                 programStats.got_only_move = 1;
8960
8961                 /* Not really, but we also use this member to
8962                    mean "line isn't going to change" (Crafty
8963                    isn't searching, so stats won't change) */
8964                 programStats.line_is_book = 1;
8965
8966                 SendProgramStatsToFrontend( cps, &programStats );
8967
8968                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8969                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8970                     DisplayMove(currentMove - 1);
8971                 }
8972                 return;
8973             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8974                               &time, &nodes, &plylev, &mvleft,
8975                               &mvtot, mvname) >= 5) {
8976                 /* The stat01: line is from Crafty (9.29+) in response
8977                    to the "." command */
8978                 programStats.seen_stat = 1;
8979                 cps->maybeThinking = TRUE;
8980
8981                 if (programStats.got_only_move || !appData.periodicUpdates)
8982                   return;
8983
8984                 programStats.depth = plylev;
8985                 programStats.time = time;
8986                 programStats.nodes = nodes;
8987                 programStats.moves_left = mvleft;
8988                 programStats.nr_moves = mvtot;
8989                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8990                 programStats.ok_to_send = 1;
8991                 programStats.movelist[0] = '\0';
8992
8993                 SendProgramStatsToFrontend( cps, &programStats );
8994
8995                 return;
8996
8997             } else if (strncmp(message,"++",2) == 0) {
8998                 /* Crafty 9.29+ outputs this */
8999                 programStats.got_fail = 2;
9000                 return;
9001
9002             } else if (strncmp(message,"--",2) == 0) {
9003                 /* Crafty 9.29+ outputs this */
9004                 programStats.got_fail = 1;
9005                 return;
9006
9007             } else if (thinkOutput[0] != NULLCHAR &&
9008                        strncmp(message, "    ", 4) == 0) {
9009                 unsigned message_len;
9010
9011                 p = message;
9012                 while (*p && *p == ' ') p++;
9013
9014                 message_len = strlen( p );
9015
9016                 /* [AS] Avoid buffer overflow */
9017                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9018                     strcat(thinkOutput, " ");
9019                     strcat(thinkOutput, p);
9020                 }
9021
9022                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9023                     strcat(programStats.movelist, " ");
9024                     strcat(programStats.movelist, p);
9025                 }
9026
9027                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9028                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9029                     DisplayMove(currentMove - 1);
9030                 }
9031                 return;
9032             }
9033         }
9034         else {
9035             buf1[0] = NULLCHAR;
9036
9037             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9038                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9039             {
9040                 ChessProgramStats cpstats;
9041
9042                 if (plyext != ' ' && plyext != '\t') {
9043                     time *= 100;
9044                 }
9045
9046                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9047                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9048                     curscore = -curscore;
9049                 }
9050
9051                 cpstats.depth = plylev;
9052                 cpstats.nodes = nodes;
9053                 cpstats.time = time;
9054                 cpstats.score = curscore;
9055                 cpstats.got_only_move = 0;
9056                 cpstats.movelist[0] = '\0';
9057
9058                 if (buf1[0] != NULLCHAR) {
9059                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9060                 }
9061
9062                 cpstats.ok_to_send = 0;
9063                 cpstats.line_is_book = 0;
9064                 cpstats.nr_moves = 0;
9065                 cpstats.moves_left = 0;
9066
9067                 SendProgramStatsToFrontend( cps, &cpstats );
9068             }
9069         }
9070     }
9071 }
9072
9073
9074 /* Parse a game score from the character string "game", and
9075    record it as the history of the current game.  The game
9076    score is NOT assumed to start from the standard position.
9077    The display is not updated in any way.
9078    */
9079 void
9080 ParseGameHistory (char *game)
9081 {
9082     ChessMove moveType;
9083     int fromX, fromY, toX, toY, boardIndex;
9084     char promoChar;
9085     char *p, *q;
9086     char buf[MSG_SIZ];
9087
9088     if (appData.debugMode)
9089       fprintf(debugFP, "Parsing game history: %s\n", game);
9090
9091     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9092     gameInfo.site = StrSave(appData.icsHost);
9093     gameInfo.date = PGNDate();
9094     gameInfo.round = StrSave("-");
9095
9096     /* Parse out names of players */
9097     while (*game == ' ') game++;
9098     p = buf;
9099     while (*game != ' ') *p++ = *game++;
9100     *p = NULLCHAR;
9101     gameInfo.white = StrSave(buf);
9102     while (*game == ' ') game++;
9103     p = buf;
9104     while (*game != ' ' && *game != '\n') *p++ = *game++;
9105     *p = NULLCHAR;
9106     gameInfo.black = StrSave(buf);
9107
9108     /* Parse moves */
9109     boardIndex = blackPlaysFirst ? 1 : 0;
9110     yynewstr(game);
9111     for (;;) {
9112         yyboardindex = boardIndex;
9113         moveType = (ChessMove) Myylex();
9114         switch (moveType) {
9115           case IllegalMove:             /* maybe suicide chess, etc. */
9116   if (appData.debugMode) {
9117     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9118     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9119     setbuf(debugFP, NULL);
9120   }
9121           case WhitePromotion:
9122           case BlackPromotion:
9123           case WhiteNonPromotion:
9124           case BlackNonPromotion:
9125           case NormalMove:
9126           case WhiteCapturesEnPassant:
9127           case BlackCapturesEnPassant:
9128           case WhiteKingSideCastle:
9129           case WhiteQueenSideCastle:
9130           case BlackKingSideCastle:
9131           case BlackQueenSideCastle:
9132           case WhiteKingSideCastleWild:
9133           case WhiteQueenSideCastleWild:
9134           case BlackKingSideCastleWild:
9135           case BlackQueenSideCastleWild:
9136           /* PUSH Fabien */
9137           case WhiteHSideCastleFR:
9138           case WhiteASideCastleFR:
9139           case BlackHSideCastleFR:
9140           case BlackASideCastleFR:
9141           /* POP Fabien */
9142             fromX = currentMoveString[0] - AAA;
9143             fromY = currentMoveString[1] - ONE;
9144             toX = currentMoveString[2] - AAA;
9145             toY = currentMoveString[3] - ONE;
9146             promoChar = currentMoveString[4];
9147             break;
9148           case WhiteDrop:
9149           case BlackDrop:
9150             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9151             fromX = moveType == WhiteDrop ?
9152               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9153             (int) CharToPiece(ToLower(currentMoveString[0]));
9154             fromY = DROP_RANK;
9155             toX = currentMoveString[2] - AAA;
9156             toY = currentMoveString[3] - ONE;
9157             promoChar = NULLCHAR;
9158             break;
9159           case AmbiguousMove:
9160             /* bug? */
9161             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9162   if (appData.debugMode) {
9163     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9164     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9165     setbuf(debugFP, NULL);
9166   }
9167             DisplayError(buf, 0);
9168             return;
9169           case ImpossibleMove:
9170             /* bug? */
9171             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9172   if (appData.debugMode) {
9173     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9174     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9175     setbuf(debugFP, NULL);
9176   }
9177             DisplayError(buf, 0);
9178             return;
9179           case EndOfFile:
9180             if (boardIndex < backwardMostMove) {
9181                 /* Oops, gap.  How did that happen? */
9182                 DisplayError(_("Gap in move list"), 0);
9183                 return;
9184             }
9185             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9186             if (boardIndex > forwardMostMove) {
9187                 forwardMostMove = boardIndex;
9188             }
9189             return;
9190           case ElapsedTime:
9191             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9192                 strcat(parseList[boardIndex-1], " ");
9193                 strcat(parseList[boardIndex-1], yy_text);
9194             }
9195             continue;
9196           case Comment:
9197           case PGNTag:
9198           case NAG:
9199           default:
9200             /* ignore */
9201             continue;
9202           case WhiteWins:
9203           case BlackWins:
9204           case GameIsDrawn:
9205           case GameUnfinished:
9206             if (gameMode == IcsExamining) {
9207                 if (boardIndex < backwardMostMove) {
9208                     /* Oops, gap.  How did that happen? */
9209                     return;
9210                 }
9211                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9212                 return;
9213             }
9214             gameInfo.result = moveType;
9215             p = strchr(yy_text, '{');
9216             if (p == NULL) p = strchr(yy_text, '(');
9217             if (p == NULL) {
9218                 p = yy_text;
9219                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9220             } else {
9221                 q = strchr(p, *p == '{' ? '}' : ')');
9222                 if (q != NULL) *q = NULLCHAR;
9223                 p++;
9224             }
9225             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9226             gameInfo.resultDetails = StrSave(p);
9227             continue;
9228         }
9229         if (boardIndex >= forwardMostMove &&
9230             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9231             backwardMostMove = blackPlaysFirst ? 1 : 0;
9232             return;
9233         }
9234         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9235                                  fromY, fromX, toY, toX, promoChar,
9236                                  parseList[boardIndex]);
9237         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9238         /* currentMoveString is set as a side-effect of yylex */
9239         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9240         strcat(moveList[boardIndex], "\n");
9241         boardIndex++;
9242         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9243         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9244           case MT_NONE:
9245           case MT_STALEMATE:
9246           default:
9247             break;
9248           case MT_CHECK:
9249             if(gameInfo.variant != VariantShogi)
9250                 strcat(parseList[boardIndex - 1], "+");
9251             break;
9252           case MT_CHECKMATE:
9253           case MT_STAINMATE:
9254             strcat(parseList[boardIndex - 1], "#");
9255             break;
9256         }
9257     }
9258 }
9259
9260
9261 /* Apply a move to the given board  */
9262 void
9263 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9264 {
9265   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9266   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9267
9268     /* [HGM] compute & store e.p. status and castling rights for new position */
9269     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9270
9271       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9272       oldEP = (signed char)board[EP_STATUS];
9273       board[EP_STATUS] = EP_NONE;
9274
9275   if (fromY == DROP_RANK) {
9276         /* must be first */
9277         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9278             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9279             return;
9280         }
9281         piece = board[toY][toX] = (ChessSquare) fromX;
9282   } else {
9283       int i;
9284
9285       if( board[toY][toX] != EmptySquare )
9286            board[EP_STATUS] = EP_CAPTURE;
9287
9288       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9289            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9290                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9291       } else
9292       if( board[fromY][fromX] == WhitePawn ) {
9293            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9294                board[EP_STATUS] = EP_PAWN_MOVE;
9295            if( toY-fromY==2) {
9296                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9297                         gameInfo.variant != VariantBerolina || toX < fromX)
9298                       board[EP_STATUS] = toX | berolina;
9299                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9300                         gameInfo.variant != VariantBerolina || toX > fromX)
9301                       board[EP_STATUS] = toX;
9302            }
9303       } else
9304       if( board[fromY][fromX] == BlackPawn ) {
9305            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9306                board[EP_STATUS] = EP_PAWN_MOVE;
9307            if( toY-fromY== -2) {
9308                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9309                         gameInfo.variant != VariantBerolina || toX < fromX)
9310                       board[EP_STATUS] = toX | berolina;
9311                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9312                         gameInfo.variant != VariantBerolina || toX > fromX)
9313                       board[EP_STATUS] = toX;
9314            }
9315        }
9316
9317        for(i=0; i<nrCastlingRights; i++) {
9318            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9319               board[CASTLING][i] == toX   && castlingRank[i] == toY
9320              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9321        }
9322
9323        if(gameInfo.variant == VariantSChess) { // update virginity
9324            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9325            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9326            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9327            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9328        }
9329
9330      if (fromX == toX && fromY == toY) return;
9331
9332      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9333      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9334      if(gameInfo.variant == VariantKnightmate)
9335          king += (int) WhiteUnicorn - (int) WhiteKing;
9336
9337     /* Code added by Tord: */
9338     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9339     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9340         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9341       board[fromY][fromX] = EmptySquare;
9342       board[toY][toX] = EmptySquare;
9343       if((toX > fromX) != (piece == WhiteRook)) {
9344         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9345       } else {
9346         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9347       }
9348     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9349                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9350       board[fromY][fromX] = EmptySquare;
9351       board[toY][toX] = EmptySquare;
9352       if((toX > fromX) != (piece == BlackRook)) {
9353         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9354       } else {
9355         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9356       }
9357     /* End of code added by Tord */
9358
9359     } else if (board[fromY][fromX] == king
9360         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9361         && toY == fromY && toX > fromX+1) {
9362         board[fromY][fromX] = EmptySquare;
9363         board[toY][toX] = king;
9364         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9365         board[fromY][BOARD_RGHT-1] = EmptySquare;
9366     } else if (board[fromY][fromX] == king
9367         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9368                && toY == fromY && toX < fromX-1) {
9369         board[fromY][fromX] = EmptySquare;
9370         board[toY][toX] = king;
9371         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9372         board[fromY][BOARD_LEFT] = EmptySquare;
9373     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9374                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9375                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9376                ) {
9377         /* white pawn promotion */
9378         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9379         if(gameInfo.variant==VariantBughouse ||
9380            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9381             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9382         board[fromY][fromX] = EmptySquare;
9383     } else if ((fromY >= BOARD_HEIGHT>>1)
9384                && (toX != fromX)
9385                && gameInfo.variant != VariantXiangqi
9386                && gameInfo.variant != VariantBerolina
9387                && (board[fromY][fromX] == WhitePawn)
9388                && (board[toY][toX] == EmptySquare)) {
9389         board[fromY][fromX] = EmptySquare;
9390         board[toY][toX] = WhitePawn;
9391         captured = board[toY - 1][toX];
9392         board[toY - 1][toX] = EmptySquare;
9393     } else if ((fromY == BOARD_HEIGHT-4)
9394                && (toX == fromX)
9395                && gameInfo.variant == VariantBerolina
9396                && (board[fromY][fromX] == WhitePawn)
9397                && (board[toY][toX] == EmptySquare)) {
9398         board[fromY][fromX] = EmptySquare;
9399         board[toY][toX] = WhitePawn;
9400         if(oldEP & EP_BEROLIN_A) {
9401                 captured = board[fromY][fromX-1];
9402                 board[fromY][fromX-1] = EmptySquare;
9403         }else{  captured = board[fromY][fromX+1];
9404                 board[fromY][fromX+1] = EmptySquare;
9405         }
9406     } else if (board[fromY][fromX] == king
9407         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9408                && toY == fromY && toX > fromX+1) {
9409         board[fromY][fromX] = EmptySquare;
9410         board[toY][toX] = king;
9411         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9412         board[fromY][BOARD_RGHT-1] = EmptySquare;
9413     } else if (board[fromY][fromX] == king
9414         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9415                && toY == fromY && toX < fromX-1) {
9416         board[fromY][fromX] = EmptySquare;
9417         board[toY][toX] = king;
9418         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9419         board[fromY][BOARD_LEFT] = EmptySquare;
9420     } else if (fromY == 7 && fromX == 3
9421                && board[fromY][fromX] == BlackKing
9422                && toY == 7 && toX == 5) {
9423         board[fromY][fromX] = EmptySquare;
9424         board[toY][toX] = BlackKing;
9425         board[fromY][7] = EmptySquare;
9426         board[toY][4] = BlackRook;
9427     } else if (fromY == 7 && fromX == 3
9428                && board[fromY][fromX] == BlackKing
9429                && toY == 7 && toX == 1) {
9430         board[fromY][fromX] = EmptySquare;
9431         board[toY][toX] = BlackKing;
9432         board[fromY][0] = EmptySquare;
9433         board[toY][2] = BlackRook;
9434     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9435                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9436                && toY < promoRank && promoChar
9437                ) {
9438         /* black pawn promotion */
9439         board[toY][toX] = CharToPiece(ToLower(promoChar));
9440         if(gameInfo.variant==VariantBughouse ||
9441            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9442             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9443         board[fromY][fromX] = EmptySquare;
9444     } else if ((fromY < BOARD_HEIGHT>>1)
9445                && (toX != fromX)
9446                && gameInfo.variant != VariantXiangqi
9447                && gameInfo.variant != VariantBerolina
9448                && (board[fromY][fromX] == BlackPawn)
9449                && (board[toY][toX] == EmptySquare)) {
9450         board[fromY][fromX] = EmptySquare;
9451         board[toY][toX] = BlackPawn;
9452         captured = board[toY + 1][toX];
9453         board[toY + 1][toX] = EmptySquare;
9454     } else if ((fromY == 3)
9455                && (toX == fromX)
9456                && gameInfo.variant == VariantBerolina
9457                && (board[fromY][fromX] == BlackPawn)
9458                && (board[toY][toX] == EmptySquare)) {
9459         board[fromY][fromX] = EmptySquare;
9460         board[toY][toX] = BlackPawn;
9461         if(oldEP & EP_BEROLIN_A) {
9462                 captured = board[fromY][fromX-1];
9463                 board[fromY][fromX-1] = EmptySquare;
9464         }else{  captured = board[fromY][fromX+1];
9465                 board[fromY][fromX+1] = EmptySquare;
9466         }
9467     } else {
9468         board[toY][toX] = board[fromY][fromX];
9469         board[fromY][fromX] = EmptySquare;
9470     }
9471   }
9472
9473     if (gameInfo.holdingsWidth != 0) {
9474
9475       /* !!A lot more code needs to be written to support holdings  */
9476       /* [HGM] OK, so I have written it. Holdings are stored in the */
9477       /* penultimate board files, so they are automaticlly stored   */
9478       /* in the game history.                                       */
9479       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9480                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9481         /* Delete from holdings, by decreasing count */
9482         /* and erasing image if necessary            */
9483         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9484         if(p < (int) BlackPawn) { /* white drop */
9485              p -= (int)WhitePawn;
9486                  p = PieceToNumber((ChessSquare)p);
9487              if(p >= gameInfo.holdingsSize) p = 0;
9488              if(--board[p][BOARD_WIDTH-2] <= 0)
9489                   board[p][BOARD_WIDTH-1] = EmptySquare;
9490              if((int)board[p][BOARD_WIDTH-2] < 0)
9491                         board[p][BOARD_WIDTH-2] = 0;
9492         } else {                  /* black drop */
9493              p -= (int)BlackPawn;
9494                  p = PieceToNumber((ChessSquare)p);
9495              if(p >= gameInfo.holdingsSize) p = 0;
9496              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9497                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9498              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9499                         board[BOARD_HEIGHT-1-p][1] = 0;
9500         }
9501       }
9502       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9503           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9504         /* [HGM] holdings: Add to holdings, if holdings exist */
9505         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9506                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9507                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9508         }
9509         p = (int) captured;
9510         if (p >= (int) BlackPawn) {
9511           p -= (int)BlackPawn;
9512           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9513                   /* in Shogi restore piece to its original  first */
9514                   captured = (ChessSquare) (DEMOTED captured);
9515                   p = DEMOTED p;
9516           }
9517           p = PieceToNumber((ChessSquare)p);
9518           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9519           board[p][BOARD_WIDTH-2]++;
9520           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9521         } else {
9522           p -= (int)WhitePawn;
9523           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9524                   captured = (ChessSquare) (DEMOTED captured);
9525                   p = DEMOTED p;
9526           }
9527           p = PieceToNumber((ChessSquare)p);
9528           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9529           board[BOARD_HEIGHT-1-p][1]++;
9530           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9531         }
9532       }
9533     } else if (gameInfo.variant == VariantAtomic) {
9534       if (captured != EmptySquare) {
9535         int y, x;
9536         for (y = toY-1; y <= toY+1; y++) {
9537           for (x = toX-1; x <= toX+1; x++) {
9538             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9539                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9540               board[y][x] = EmptySquare;
9541             }
9542           }
9543         }
9544         board[toY][toX] = EmptySquare;
9545       }
9546     }
9547     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9548         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9549     } else
9550     if(promoChar == '+') {
9551         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9552         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9553     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9554         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9555         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9556            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9557         board[toY][toX] = newPiece;
9558     }
9559     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9560                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9561         // [HGM] superchess: take promotion piece out of holdings
9562         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9563         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9564             if(!--board[k][BOARD_WIDTH-2])
9565                 board[k][BOARD_WIDTH-1] = EmptySquare;
9566         } else {
9567             if(!--board[BOARD_HEIGHT-1-k][1])
9568                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9569         }
9570     }
9571
9572 }
9573
9574 /* Updates forwardMostMove */
9575 void
9576 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9577 {
9578 //    forwardMostMove++; // [HGM] bare: moved downstream
9579
9580     (void) CoordsToAlgebraic(boards[forwardMostMove],
9581                              PosFlags(forwardMostMove),
9582                              fromY, fromX, toY, toX, promoChar,
9583                              parseList[forwardMostMove]);
9584
9585     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9586         int timeLeft; static int lastLoadFlag=0; int king, piece;
9587         piece = boards[forwardMostMove][fromY][fromX];
9588         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9589         if(gameInfo.variant == VariantKnightmate)
9590             king += (int) WhiteUnicorn - (int) WhiteKing;
9591         if(forwardMostMove == 0) {
9592             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9593                 fprintf(serverMoves, "%s;", UserName());
9594             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9595                 fprintf(serverMoves, "%s;", second.tidy);
9596             fprintf(serverMoves, "%s;", first.tidy);
9597             if(gameMode == MachinePlaysWhite)
9598                 fprintf(serverMoves, "%s;", UserName());
9599             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9600                 fprintf(serverMoves, "%s;", second.tidy);
9601         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9602         lastLoadFlag = loadFlag;
9603         // print base move
9604         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9605         // print castling suffix
9606         if( toY == fromY && piece == king ) {
9607             if(toX-fromX > 1)
9608                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9609             if(fromX-toX >1)
9610                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9611         }
9612         // e.p. suffix
9613         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9614              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9615              boards[forwardMostMove][toY][toX] == EmptySquare
9616              && fromX != toX && fromY != toY)
9617                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9618         // promotion suffix
9619         if(promoChar != NULLCHAR) {
9620             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9621                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9622                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9623             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9624         }
9625         if(!loadFlag) {
9626                 char buf[MOVE_LEN*2], *p; int len;
9627             fprintf(serverMoves, "/%d/%d",
9628                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9629             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9630             else                      timeLeft = blackTimeRemaining/1000;
9631             fprintf(serverMoves, "/%d", timeLeft);
9632                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9633                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9634                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9635                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9636             fprintf(serverMoves, "/%s", buf);
9637         }
9638         fflush(serverMoves);
9639     }
9640
9641     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9642         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9643       return;
9644     }
9645     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9646     if (commentList[forwardMostMove+1] != NULL) {
9647         free(commentList[forwardMostMove+1]);
9648         commentList[forwardMostMove+1] = NULL;
9649     }
9650     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9651     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9652     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9653     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9654     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9655     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9656     adjustedClock = FALSE;
9657     gameInfo.result = GameUnfinished;
9658     if (gameInfo.resultDetails != NULL) {
9659         free(gameInfo.resultDetails);
9660         gameInfo.resultDetails = NULL;
9661     }
9662     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9663                               moveList[forwardMostMove - 1]);
9664     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9665       case MT_NONE:
9666       case MT_STALEMATE:
9667       default:
9668         break;
9669       case MT_CHECK:
9670         if(gameInfo.variant != VariantShogi)
9671             strcat(parseList[forwardMostMove - 1], "+");
9672         break;
9673       case MT_CHECKMATE:
9674       case MT_STAINMATE:
9675         strcat(parseList[forwardMostMove - 1], "#");
9676         break;
9677     }
9678
9679 }
9680
9681 /* Updates currentMove if not pausing */
9682 void
9683 ShowMove (int fromX, int fromY, int toX, int toY)
9684 {
9685     int instant = (gameMode == PlayFromGameFile) ?
9686         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9687     if(appData.noGUI) return;
9688     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9689         if (!instant) {
9690             if (forwardMostMove == currentMove + 1) {
9691                 AnimateMove(boards[forwardMostMove - 1],
9692                             fromX, fromY, toX, toY);
9693             }
9694             if (appData.highlightLastMove) {
9695                 SetHighlights(fromX, fromY, toX, toY);
9696             }
9697         }
9698         currentMove = forwardMostMove;
9699     }
9700
9701     if (instant) return;
9702
9703     DisplayMove(currentMove - 1);
9704     DrawPosition(FALSE, boards[currentMove]);
9705     DisplayBothClocks();
9706     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9707 }
9708
9709 void
9710 SendEgtPath (ChessProgramState *cps)
9711 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9712         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9713
9714         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9715
9716         while(*p) {
9717             char c, *q = name+1, *r, *s;
9718
9719             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9720             while(*p && *p != ',') *q++ = *p++;
9721             *q++ = ':'; *q = 0;
9722             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9723                 strcmp(name, ",nalimov:") == 0 ) {
9724                 // take nalimov path from the menu-changeable option first, if it is defined
9725               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9726                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9727             } else
9728             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9729                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9730                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9731                 s = r = StrStr(s, ":") + 1; // beginning of path info
9732                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9733                 c = *r; *r = 0;             // temporarily null-terminate path info
9734                     *--q = 0;               // strip of trailig ':' from name
9735                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9736                 *r = c;
9737                 SendToProgram(buf,cps);     // send egtbpath command for this format
9738             }
9739             if(*p == ',') p++; // read away comma to position for next format name
9740         }
9741 }
9742
9743 void
9744 InitChessProgram (ChessProgramState *cps, int setup)
9745 /* setup needed to setup FRC opening position */
9746 {
9747     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9748     if (appData.noChessProgram) return;
9749     hintRequested = FALSE;
9750     bookRequested = FALSE;
9751
9752     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9753     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9754     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9755     if(cps->memSize) { /* [HGM] memory */
9756       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9757         SendToProgram(buf, cps);
9758     }
9759     SendEgtPath(cps); /* [HGM] EGT */
9760     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9761       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9762         SendToProgram(buf, cps);
9763     }
9764
9765     SendToProgram(cps->initString, cps);
9766     if (gameInfo.variant != VariantNormal &&
9767         gameInfo.variant != VariantLoadable
9768         /* [HGM] also send variant if board size non-standard */
9769         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9770                                             ) {
9771       char *v = VariantName(gameInfo.variant);
9772       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9773         /* [HGM] in protocol 1 we have to assume all variants valid */
9774         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9775         DisplayFatalError(buf, 0, 1);
9776         return;
9777       }
9778
9779       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9780       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9781       if( gameInfo.variant == VariantXiangqi )
9782            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9783       if( gameInfo.variant == VariantShogi )
9784            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9785       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9786            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9787       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9788           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9789            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9790       if( gameInfo.variant == VariantCourier )
9791            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9792       if( gameInfo.variant == VariantSuper )
9793            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9794       if( gameInfo.variant == VariantGreat )
9795            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9796       if( gameInfo.variant == VariantSChess )
9797            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9798       if( gameInfo.variant == VariantGrand )
9799            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9800
9801       if(overruled) {
9802         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9803                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9804            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9805            if(StrStr(cps->variants, b) == NULL) {
9806                // specific sized variant not known, check if general sizing allowed
9807                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9808                    if(StrStr(cps->variants, "boardsize") == NULL) {
9809                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9810                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9811                        DisplayFatalError(buf, 0, 1);
9812                        return;
9813                    }
9814                    /* [HGM] here we really should compare with the maximum supported board size */
9815                }
9816            }
9817       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9818       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9819       SendToProgram(buf, cps);
9820     }
9821     currentlyInitializedVariant = gameInfo.variant;
9822
9823     /* [HGM] send opening position in FRC to first engine */
9824     if(setup) {
9825           SendToProgram("force\n", cps);
9826           SendBoard(cps, 0);
9827           /* engine is now in force mode! Set flag to wake it up after first move. */
9828           setboardSpoiledMachineBlack = 1;
9829     }
9830
9831     if (cps->sendICS) {
9832       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9833       SendToProgram(buf, cps);
9834     }
9835     cps->maybeThinking = FALSE;
9836     cps->offeredDraw = 0;
9837     if (!appData.icsActive) {
9838         SendTimeControl(cps, movesPerSession, timeControl,
9839                         timeIncrement, appData.searchDepth,
9840                         searchTime);
9841     }
9842     if (appData.showThinking
9843         // [HGM] thinking: four options require thinking output to be sent
9844         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9845                                 ) {
9846         SendToProgram("post\n", cps);
9847     }
9848     SendToProgram("hard\n", cps);
9849     if (!appData.ponderNextMove) {
9850         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9851            it without being sure what state we are in first.  "hard"
9852            is not a toggle, so that one is OK.
9853          */
9854         SendToProgram("easy\n", cps);
9855     }
9856     if (cps->usePing) {
9857       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9858       SendToProgram(buf, cps);
9859     }
9860     cps->initDone = TRUE;
9861     ClearEngineOutputPane(cps == &second);
9862 }
9863
9864
9865 void
9866 StartChessProgram (ChessProgramState *cps)
9867 {
9868     char buf[MSG_SIZ];
9869     int err;
9870
9871     if (appData.noChessProgram) return;
9872     cps->initDone = FALSE;
9873
9874     if (strcmp(cps->host, "localhost") == 0) {
9875         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9876     } else if (*appData.remoteShell == NULLCHAR) {
9877         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9878     } else {
9879         if (*appData.remoteUser == NULLCHAR) {
9880           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9881                     cps->program);
9882         } else {
9883           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9884                     cps->host, appData.remoteUser, cps->program);
9885         }
9886         err = StartChildProcess(buf, "", &cps->pr);
9887     }
9888
9889     if (err != 0) {
9890       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9891         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9892         if(cps != &first) return;
9893         appData.noChessProgram = TRUE;
9894         ThawUI();
9895         SetNCPMode();
9896 //      DisplayFatalError(buf, err, 1);
9897 //      cps->pr = NoProc;
9898 //      cps->isr = NULL;
9899         return;
9900     }
9901
9902     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9903     if (cps->protocolVersion > 1) {
9904       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9905       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9906       cps->comboCnt = 0;  //                and values of combo boxes
9907       SendToProgram(buf, cps);
9908     } else {
9909       SendToProgram("xboard\n", cps);
9910     }
9911 }
9912
9913 void
9914 TwoMachinesEventIfReady P((void))
9915 {
9916   static int curMess = 0;
9917   if (first.lastPing != first.lastPong) {
9918     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9919     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9920     return;
9921   }
9922   if (second.lastPing != second.lastPong) {
9923     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9924     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9925     return;
9926   }
9927   DisplayMessage("", ""); curMess = 0;
9928   ThawUI();
9929   TwoMachinesEvent();
9930 }
9931
9932 char *
9933 MakeName (char *template)
9934 {
9935     time_t clock;
9936     struct tm *tm;
9937     static char buf[MSG_SIZ];
9938     char *p = buf;
9939     int i;
9940
9941     clock = time((time_t *)NULL);
9942     tm = localtime(&clock);
9943
9944     while(*p++ = *template++) if(p[-1] == '%') {
9945         switch(*template++) {
9946           case 0:   *p = 0; return buf;
9947           case 'Y': i = tm->tm_year+1900; break;
9948           case 'y': i = tm->tm_year-100; break;
9949           case 'M': i = tm->tm_mon+1; break;
9950           case 'd': i = tm->tm_mday; break;
9951           case 'h': i = tm->tm_hour; break;
9952           case 'm': i = tm->tm_min; break;
9953           case 's': i = tm->tm_sec; break;
9954           default:  i = 0;
9955         }
9956         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9957     }
9958     return buf;
9959 }
9960
9961 int
9962 CountPlayers (char *p)
9963 {
9964     int n = 0;
9965     while(p = strchr(p, '\n')) p++, n++; // count participants
9966     return n;
9967 }
9968
9969 FILE *
9970 WriteTourneyFile (char *results, FILE *f)
9971 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9972     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9973     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9974         // create a file with tournament description
9975         fprintf(f, "-participants {%s}\n", appData.participants);
9976         fprintf(f, "-seedBase %d\n", appData.seedBase);
9977         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9978         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9979         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9980         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9981         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9982         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9983         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9984         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9985         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9986         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9987         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9988         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9989         if(searchTime > 0)
9990                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9991         else {
9992                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9993                 fprintf(f, "-tc %s\n", appData.timeControl);
9994                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9995         }
9996         fprintf(f, "-results \"%s\"\n", results);
9997     }
9998     return f;
9999 }
10000
10001 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10002
10003 void
10004 Substitute (char *participants, int expunge)
10005 {
10006     int i, changed, changes=0, nPlayers=0;
10007     char *p, *q, *r, buf[MSG_SIZ];
10008     if(participants == NULL) return;
10009     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10010     r = p = participants; q = appData.participants;
10011     while(*p && *p == *q) {
10012         if(*p == '\n') r = p+1, nPlayers++;
10013         p++; q++;
10014     }
10015     if(*p) { // difference
10016         while(*p && *p++ != '\n');
10017         while(*q && *q++ != '\n');
10018       changed = nPlayers;
10019         changes = 1 + (strcmp(p, q) != 0);
10020     }
10021     if(changes == 1) { // a single engine mnemonic was changed
10022         q = r; while(*q) nPlayers += (*q++ == '\n');
10023         p = buf; while(*r && (*p = *r++) != '\n') p++;
10024         *p = NULLCHAR;
10025         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10026         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10027         if(mnemonic[i]) { // The substitute is valid
10028             FILE *f;
10029             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10030                 flock(fileno(f), LOCK_EX);
10031                 ParseArgsFromFile(f);
10032                 fseek(f, 0, SEEK_SET);
10033                 FREE(appData.participants); appData.participants = participants;
10034                 if(expunge) { // erase results of replaced engine
10035                     int len = strlen(appData.results), w, b, dummy;
10036                     for(i=0; i<len; i++) {
10037                         Pairing(i, nPlayers, &w, &b, &dummy);
10038                         if((w == changed || b == changed) && appData.results[i] == '*') {
10039                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10040                             fclose(f);
10041                             return;
10042                         }
10043                     }
10044                     for(i=0; i<len; i++) {
10045                         Pairing(i, nPlayers, &w, &b, &dummy);
10046                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10047                     }
10048                 }
10049                 WriteTourneyFile(appData.results, f);
10050                 fclose(f); // release lock
10051                 return;
10052             }
10053         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10054     }
10055     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10056     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10057     free(participants);
10058     return;
10059 }
10060
10061 int
10062 CheckPlayers (char *participants)
10063 {
10064         int i;
10065         char buf[MSG_SIZ], *p;
10066         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10067         while(p = strchr(participants, '\n')) {
10068             *p = NULLCHAR;
10069             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10070             if(!mnemonic[i]) {
10071                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10072                 *p = '\n';
10073                 DisplayError(buf, 0);
10074                 return 1;
10075             }
10076             *p = '\n';
10077             participants = p + 1;
10078         }
10079         return 0;
10080 }
10081
10082 int
10083 CreateTourney (char *name)
10084 {
10085         FILE *f;
10086         if(matchMode && strcmp(name, appData.tourneyFile)) {
10087              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10088         }
10089         if(name[0] == NULLCHAR) {
10090             if(appData.participants[0])
10091                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10092             return 0;
10093         }
10094         f = fopen(name, "r");
10095         if(f) { // file exists
10096             ASSIGN(appData.tourneyFile, name);
10097             ParseArgsFromFile(f); // parse it
10098         } else {
10099             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10100             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10101                 DisplayError(_("Not enough participants"), 0);
10102                 return 0;
10103             }
10104             if(CheckPlayers(appData.participants)) return 0;
10105             ASSIGN(appData.tourneyFile, name);
10106             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10107             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10108         }
10109         fclose(f);
10110         appData.noChessProgram = FALSE;
10111         appData.clockMode = TRUE;
10112         SetGNUMode();
10113         return 1;
10114 }
10115
10116 int
10117 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10118 {
10119     char buf[MSG_SIZ], *p, *q;
10120     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10121     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10122     skip = !all && group[0]; // if group requested, we start in skip mode
10123     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10124         p = names; q = buf; header = 0;
10125         while(*p && *p != '\n') *q++ = *p++;
10126         *q = 0;
10127         if(*p == '\n') p++;
10128         if(buf[0] == '#') {
10129             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10130             depth++; // we must be entering a new group
10131             if(all) continue; // suppress printing group headers when complete list requested
10132             header = 1;
10133             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10134         }
10135         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10136         if(engineList[i]) free(engineList[i]);
10137         engineList[i] = strdup(buf);
10138         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10139         if(engineMnemonic[i]) free(engineMnemonic[i]);
10140         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10141             strcat(buf, " (");
10142             sscanf(q + 8, "%s", buf + strlen(buf));
10143             strcat(buf, ")");
10144         }
10145         engineMnemonic[i] = strdup(buf);
10146         i++;
10147     }
10148     engineList[i] = engineMnemonic[i] = NULL;
10149     return i;
10150 }
10151
10152 // following implemented as macro to avoid type limitations
10153 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10154
10155 void
10156 SwapEngines (int n)
10157 {   // swap settings for first engine and other engine (so far only some selected options)
10158     int h;
10159     char *p;
10160     if(n == 0) return;
10161     SWAP(directory, p)
10162     SWAP(chessProgram, p)
10163     SWAP(isUCI, h)
10164     SWAP(hasOwnBookUCI, h)
10165     SWAP(protocolVersion, h)
10166     SWAP(reuse, h)
10167     SWAP(scoreIsAbsolute, h)
10168     SWAP(timeOdds, h)
10169     SWAP(logo, p)
10170     SWAP(pgnName, p)
10171     SWAP(pvSAN, h)
10172     SWAP(engOptions, p)
10173     SWAP(engInitString, p)
10174     SWAP(computerString, p)
10175     SWAP(features, p)
10176     SWAP(fenOverride, p)
10177     SWAP(NPS, h)
10178     SWAP(accumulateTC, h)
10179     SWAP(host, p)
10180 }
10181
10182 int
10183 SetPlayer (int player, char *p)
10184 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10185     int i;
10186     char buf[MSG_SIZ], *engineName;
10187     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10188     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10189     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10190     if(mnemonic[i]) {
10191         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10192         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10193         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10194         ParseArgsFromString(buf);
10195     }
10196     free(engineName);
10197     return i;
10198 }
10199
10200 char *recentEngines;
10201
10202 void
10203 RecentEngineEvent (int nr)
10204 {
10205     int n;
10206 //    SwapEngines(1); // bump first to second
10207 //    ReplaceEngine(&second, 1); // and load it there
10208     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10209     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10210     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10211         ReplaceEngine(&first, 0);
10212         FloatToFront(&appData.recentEngineList, command[n]);
10213     }
10214 }
10215
10216 int
10217 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10218 {   // determine players from game number
10219     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10220
10221     if(appData.tourneyType == 0) {
10222         roundsPerCycle = (nPlayers - 1) | 1;
10223         pairingsPerRound = nPlayers / 2;
10224     } else if(appData.tourneyType > 0) {
10225         roundsPerCycle = nPlayers - appData.tourneyType;
10226         pairingsPerRound = appData.tourneyType;
10227     }
10228     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10229     gamesPerCycle = gamesPerRound * roundsPerCycle;
10230     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10231     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10232     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10233     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10234     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10235     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10236
10237     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10238     if(appData.roundSync) *syncInterval = gamesPerRound;
10239
10240     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10241
10242     if(appData.tourneyType == 0) {
10243         if(curPairing == (nPlayers-1)/2 ) {
10244             *whitePlayer = curRound;
10245             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10246         } else {
10247             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10248             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10249             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10250             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10251         }
10252     } else if(appData.tourneyType > 1) {
10253         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10254         *whitePlayer = curRound + appData.tourneyType;
10255     } else if(appData.tourneyType > 0) {
10256         *whitePlayer = curPairing;
10257         *blackPlayer = curRound + appData.tourneyType;
10258     }
10259
10260     // take care of white/black alternation per round. 
10261     // For cycles and games this is already taken care of by default, derived from matchGame!
10262     return curRound & 1;
10263 }
10264
10265 int
10266 NextTourneyGame (int nr, int *swapColors)
10267 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10268     char *p, *q;
10269     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10270     FILE *tf;
10271     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10272     tf = fopen(appData.tourneyFile, "r");
10273     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10274     ParseArgsFromFile(tf); fclose(tf);
10275     InitTimeControls(); // TC might be altered from tourney file
10276
10277     nPlayers = CountPlayers(appData.participants); // count participants
10278     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10279     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10280
10281     if(syncInterval) {
10282         p = q = appData.results;
10283         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10284         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10285             DisplayMessage(_("Waiting for other game(s)"),"");
10286             waitingForGame = TRUE;
10287             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10288             return 0;
10289         }
10290         waitingForGame = FALSE;
10291     }
10292
10293     if(appData.tourneyType < 0) {
10294         if(nr>=0 && !pairingReceived) {
10295             char buf[1<<16];
10296             if(pairing.pr == NoProc) {
10297                 if(!appData.pairingEngine[0]) {
10298                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10299                     return 0;
10300                 }
10301                 StartChessProgram(&pairing); // starts the pairing engine
10302             }
10303             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10304             SendToProgram(buf, &pairing);
10305             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10306             SendToProgram(buf, &pairing);
10307             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10308         }
10309         pairingReceived = 0;                              // ... so we continue here 
10310         *swapColors = 0;
10311         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10312         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10313         matchGame = 1; roundNr = nr / syncInterval + 1;
10314     }
10315
10316     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10317
10318     // redefine engines, engine dir, etc.
10319     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10320     if(first.pr == NoProc) {
10321       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10322       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10323     }
10324     if(second.pr == NoProc) {
10325       SwapEngines(1);
10326       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10327       SwapEngines(1);         // and make that valid for second engine by swapping
10328       InitEngine(&second, 1);
10329     }
10330     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10331     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10332     return 1;
10333 }
10334
10335 void
10336 NextMatchGame ()
10337 {   // performs game initialization that does not invoke engines, and then tries to start the game
10338     int res, firstWhite, swapColors = 0;
10339     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10340     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
10341         char buf[MSG_SIZ];
10342         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10343         if(strcmp(buf, currentDebugFile)) { // name has changed
10344             FILE *f = fopen(buf, "w");
10345             if(f) { // if opening the new file failed, just keep using the old one
10346                 ASSIGN(currentDebugFile, buf);
10347                 fclose(debugFP);
10348                 debugFP = f;
10349             }
10350             if(appData.serverFileName) {
10351                 if(serverFP) fclose(serverFP);
10352                 serverFP = fopen(appData.serverFileName, "w");
10353                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10354                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10355             }
10356         }
10357     }
10358     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10359     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10360     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10361     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10362     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10363     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10364     Reset(FALSE, first.pr != NoProc);
10365     res = LoadGameOrPosition(matchGame); // setup game
10366     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10367     if(!res) return; // abort when bad game/pos file
10368     TwoMachinesEvent();
10369 }
10370
10371 void
10372 UserAdjudicationEvent (int result)
10373 {
10374     ChessMove gameResult = GameIsDrawn;
10375
10376     if( result > 0 ) {
10377         gameResult = WhiteWins;
10378     }
10379     else if( result < 0 ) {
10380         gameResult = BlackWins;
10381     }
10382
10383     if( gameMode == TwoMachinesPlay ) {
10384         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10385     }
10386 }
10387
10388
10389 // [HGM] save: calculate checksum of game to make games easily identifiable
10390 int
10391 StringCheckSum (char *s)
10392 {
10393         int i = 0;
10394         if(s==NULL) return 0;
10395         while(*s) i = i*259 + *s++;
10396         return i;
10397 }
10398
10399 int
10400 GameCheckSum ()
10401 {
10402         int i, sum=0;
10403         for(i=backwardMostMove; i<forwardMostMove; i++) {
10404                 sum += pvInfoList[i].depth;
10405                 sum += StringCheckSum(parseList[i]);
10406                 sum += StringCheckSum(commentList[i]);
10407                 sum *= 261;
10408         }
10409         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10410         return sum + StringCheckSum(commentList[i]);
10411 } // end of save patch
10412
10413 void
10414 GameEnds (ChessMove result, char *resultDetails, int whosays)
10415 {
10416     GameMode nextGameMode;
10417     int isIcsGame;
10418     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10419
10420     if(endingGame) return; /* [HGM] crash: forbid recursion */
10421     endingGame = 1;
10422     if(twoBoards) { // [HGM] dual: switch back to one board
10423         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10424         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10425     }
10426     if (appData.debugMode) {
10427       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10428               result, resultDetails ? resultDetails : "(null)", whosays);
10429     }
10430
10431     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10432
10433     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10434         /* If we are playing on ICS, the server decides when the
10435            game is over, but the engine can offer to draw, claim
10436            a draw, or resign.
10437          */
10438 #if ZIPPY
10439         if (appData.zippyPlay && first.initDone) {
10440             if (result == GameIsDrawn) {
10441                 /* In case draw still needs to be claimed */
10442                 SendToICS(ics_prefix);
10443                 SendToICS("draw\n");
10444             } else if (StrCaseStr(resultDetails, "resign")) {
10445                 SendToICS(ics_prefix);
10446                 SendToICS("resign\n");
10447             }
10448         }
10449 #endif
10450         endingGame = 0; /* [HGM] crash */
10451         return;
10452     }
10453
10454     /* If we're loading the game from a file, stop */
10455     if (whosays == GE_FILE) {
10456       (void) StopLoadGameTimer();
10457       gameFileFP = NULL;
10458     }
10459
10460     /* Cancel draw offers */
10461     first.offeredDraw = second.offeredDraw = 0;
10462
10463     /* If this is an ICS game, only ICS can really say it's done;
10464        if not, anyone can. */
10465     isIcsGame = (gameMode == IcsPlayingWhite ||
10466                  gameMode == IcsPlayingBlack ||
10467                  gameMode == IcsObserving    ||
10468                  gameMode == IcsExamining);
10469
10470     if (!isIcsGame || whosays == GE_ICS) {
10471         /* OK -- not an ICS game, or ICS said it was done */
10472         StopClocks();
10473         if (!isIcsGame && !appData.noChessProgram)
10474           SetUserThinkingEnables();
10475
10476         /* [HGM] if a machine claims the game end we verify this claim */
10477         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10478             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10479                 char claimer;
10480                 ChessMove trueResult = (ChessMove) -1;
10481
10482                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10483                                             first.twoMachinesColor[0] :
10484                                             second.twoMachinesColor[0] ;
10485
10486                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10487                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10488                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10489                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10490                 } else
10491                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10492                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10493                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10494                 } else
10495                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10496                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10497                 }
10498
10499                 // now verify win claims, but not in drop games, as we don't understand those yet
10500                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10501                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10502                     (result == WhiteWins && claimer == 'w' ||
10503                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10504                       if (appData.debugMode) {
10505                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10506                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10507                       }
10508                       if(result != trueResult) {
10509                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10510                               result = claimer == 'w' ? BlackWins : WhiteWins;
10511                               resultDetails = buf;
10512                       }
10513                 } else
10514                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10515                     && (forwardMostMove <= backwardMostMove ||
10516                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10517                         (claimer=='b')==(forwardMostMove&1))
10518                                                                                   ) {
10519                       /* [HGM] verify: draws that were not flagged are false claims */
10520                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10521                       result = claimer == 'w' ? BlackWins : WhiteWins;
10522                       resultDetails = buf;
10523                 }
10524                 /* (Claiming a loss is accepted no questions asked!) */
10525             }
10526             /* [HGM] bare: don't allow bare King to win */
10527             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10528                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10529                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10530                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10531                && result != GameIsDrawn)
10532             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10533                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10534                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10535                         if(p >= 0 && p <= (int)WhiteKing) k++;
10536                 }
10537                 if (appData.debugMode) {
10538                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10539                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10540                 }
10541                 if(k <= 1) {
10542                         result = GameIsDrawn;
10543                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10544                         resultDetails = buf;
10545                 }
10546             }
10547         }
10548
10549
10550         if(serverMoves != NULL && !loadFlag) { char c = '=';
10551             if(result==WhiteWins) c = '+';
10552             if(result==BlackWins) c = '-';
10553             if(resultDetails != NULL)
10554                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10555         }
10556         if (resultDetails != NULL) {
10557             gameInfo.result = result;
10558             gameInfo.resultDetails = StrSave(resultDetails);
10559
10560             /* display last move only if game was not loaded from file */
10561             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10562                 DisplayMove(currentMove - 1);
10563
10564             if (forwardMostMove != 0) {
10565                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10566                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10567                                                                 ) {
10568                     if (*appData.saveGameFile != NULLCHAR) {
10569                         SaveGameToFile(appData.saveGameFile, TRUE);
10570                     } else if (appData.autoSaveGames) {
10571                         AutoSaveGame();
10572                     }
10573                     if (*appData.savePositionFile != NULLCHAR) {
10574                         SavePositionToFile(appData.savePositionFile);
10575                     }
10576                 }
10577             }
10578
10579             /* Tell program how game ended in case it is learning */
10580             /* [HGM] Moved this to after saving the PGN, just in case */
10581             /* engine died and we got here through time loss. In that */
10582             /* case we will get a fatal error writing the pipe, which */
10583             /* would otherwise lose us the PGN.                       */
10584             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10585             /* output during GameEnds should never be fatal anymore   */
10586             if (gameMode == MachinePlaysWhite ||
10587                 gameMode == MachinePlaysBlack ||
10588                 gameMode == TwoMachinesPlay ||
10589                 gameMode == IcsPlayingWhite ||
10590                 gameMode == IcsPlayingBlack ||
10591                 gameMode == BeginningOfGame) {
10592                 char buf[MSG_SIZ];
10593                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10594                         resultDetails);
10595                 if (first.pr != NoProc) {
10596                     SendToProgram(buf, &first);
10597                 }
10598                 if (second.pr != NoProc &&
10599                     gameMode == TwoMachinesPlay) {
10600                     SendToProgram(buf, &second);
10601                 }
10602             }
10603         }
10604
10605         if (appData.icsActive) {
10606             if (appData.quietPlay &&
10607                 (gameMode == IcsPlayingWhite ||
10608                  gameMode == IcsPlayingBlack)) {
10609                 SendToICS(ics_prefix);
10610                 SendToICS("set shout 1\n");
10611             }
10612             nextGameMode = IcsIdle;
10613             ics_user_moved = FALSE;
10614             /* clean up premove.  It's ugly when the game has ended and the
10615              * premove highlights are still on the board.
10616              */
10617             if (gotPremove) {
10618               gotPremove = FALSE;
10619               ClearPremoveHighlights();
10620               DrawPosition(FALSE, boards[currentMove]);
10621             }
10622             if (whosays == GE_ICS) {
10623                 switch (result) {
10624                 case WhiteWins:
10625                     if (gameMode == IcsPlayingWhite)
10626                         PlayIcsWinSound();
10627                     else if(gameMode == IcsPlayingBlack)
10628                         PlayIcsLossSound();
10629                     break;
10630                 case BlackWins:
10631                     if (gameMode == IcsPlayingBlack)
10632                         PlayIcsWinSound();
10633                     else if(gameMode == IcsPlayingWhite)
10634                         PlayIcsLossSound();
10635                     break;
10636                 case GameIsDrawn:
10637                     PlayIcsDrawSound();
10638                     break;
10639                 default:
10640                     PlayIcsUnfinishedSound();
10641                 }
10642             }
10643         } else if (gameMode == EditGame ||
10644                    gameMode == PlayFromGameFile ||
10645                    gameMode == AnalyzeMode ||
10646                    gameMode == AnalyzeFile) {
10647             nextGameMode = gameMode;
10648         } else {
10649             nextGameMode = EndOfGame;
10650         }
10651         pausing = FALSE;
10652         ModeHighlight();
10653     } else {
10654         nextGameMode = gameMode;
10655     }
10656
10657     if (appData.noChessProgram) {
10658         gameMode = nextGameMode;
10659         ModeHighlight();
10660         endingGame = 0; /* [HGM] crash */
10661         return;
10662     }
10663
10664     if (first.reuse) {
10665         /* Put first chess program into idle state */
10666         if (first.pr != NoProc &&
10667             (gameMode == MachinePlaysWhite ||
10668              gameMode == MachinePlaysBlack ||
10669              gameMode == TwoMachinesPlay ||
10670              gameMode == IcsPlayingWhite ||
10671              gameMode == IcsPlayingBlack ||
10672              gameMode == BeginningOfGame)) {
10673             SendToProgram("force\n", &first);
10674             if (first.usePing) {
10675               char buf[MSG_SIZ];
10676               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10677               SendToProgram(buf, &first);
10678             }
10679         }
10680     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10681         /* Kill off first chess program */
10682         if (first.isr != NULL)
10683           RemoveInputSource(first.isr);
10684         first.isr = NULL;
10685
10686         if (first.pr != NoProc) {
10687             ExitAnalyzeMode();
10688             DoSleep( appData.delayBeforeQuit );
10689             SendToProgram("quit\n", &first);
10690             DoSleep( appData.delayAfterQuit );
10691             DestroyChildProcess(first.pr, first.useSigterm);
10692         }
10693         first.pr = NoProc;
10694     }
10695     if (second.reuse) {
10696         /* Put second chess program into idle state */
10697         if (second.pr != NoProc &&
10698             gameMode == TwoMachinesPlay) {
10699             SendToProgram("force\n", &second);
10700             if (second.usePing) {
10701               char buf[MSG_SIZ];
10702               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10703               SendToProgram(buf, &second);
10704             }
10705         }
10706     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10707         /* Kill off second chess program */
10708         if (second.isr != NULL)
10709           RemoveInputSource(second.isr);
10710         second.isr = NULL;
10711
10712         if (second.pr != NoProc) {
10713             DoSleep( appData.delayBeforeQuit );
10714             SendToProgram("quit\n", &second);
10715             DoSleep( appData.delayAfterQuit );
10716             DestroyChildProcess(second.pr, second.useSigterm);
10717         }
10718         second.pr = NoProc;
10719     }
10720
10721     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10722         char resChar = '=';
10723         switch (result) {
10724         case WhiteWins:
10725           resChar = '+';
10726           if (first.twoMachinesColor[0] == 'w') {
10727             first.matchWins++;
10728           } else {
10729             second.matchWins++;
10730           }
10731           break;
10732         case BlackWins:
10733           resChar = '-';
10734           if (first.twoMachinesColor[0] == 'b') {
10735             first.matchWins++;
10736           } else {
10737             second.matchWins++;
10738           }
10739           break;
10740         case GameUnfinished:
10741           resChar = ' ';
10742         default:
10743           break;
10744         }
10745
10746         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10747         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10748             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10749             ReserveGame(nextGame, resChar); // sets nextGame
10750             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10751             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10752         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10753
10754         if (nextGame <= appData.matchGames && !abortMatch) {
10755             gameMode = nextGameMode;
10756             matchGame = nextGame; // this will be overruled in tourney mode!
10757             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10758             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10759             endingGame = 0; /* [HGM] crash */
10760             return;
10761         } else {
10762             gameMode = nextGameMode;
10763             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10764                      first.tidy, second.tidy,
10765                      first.matchWins, second.matchWins,
10766                      appData.matchGames - (first.matchWins + second.matchWins));
10767             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10768             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10769             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10770             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10771                 first.twoMachinesColor = "black\n";
10772                 second.twoMachinesColor = "white\n";
10773             } else {
10774                 first.twoMachinesColor = "white\n";
10775                 second.twoMachinesColor = "black\n";
10776             }
10777         }
10778     }
10779     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10780         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10781       ExitAnalyzeMode();
10782     gameMode = nextGameMode;
10783     ModeHighlight();
10784     endingGame = 0;  /* [HGM] crash */
10785     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10786         if(matchMode == TRUE) { // match through command line: exit with or without popup
10787             if(ranking) {
10788                 ToNrEvent(forwardMostMove);
10789                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10790                 else ExitEvent(0);
10791             } else DisplayFatalError(buf, 0, 0);
10792         } else { // match through menu; just stop, with or without popup
10793             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10794             ModeHighlight();
10795             if(ranking){
10796                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10797             } else DisplayNote(buf);
10798       }
10799       if(ranking) free(ranking);
10800     }
10801 }
10802
10803 /* Assumes program was just initialized (initString sent).
10804    Leaves program in force mode. */
10805 void
10806 FeedMovesToProgram (ChessProgramState *cps, int upto)
10807 {
10808     int i;
10809
10810     if (appData.debugMode)
10811       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10812               startedFromSetupPosition ? "position and " : "",
10813               backwardMostMove, upto, cps->which);
10814     if(currentlyInitializedVariant != gameInfo.variant) {
10815       char buf[MSG_SIZ];
10816         // [HGM] variantswitch: make engine aware of new variant
10817         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10818                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10819         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10820         SendToProgram(buf, cps);
10821         currentlyInitializedVariant = gameInfo.variant;
10822     }
10823     SendToProgram("force\n", cps);
10824     if (startedFromSetupPosition) {
10825         SendBoard(cps, backwardMostMove);
10826     if (appData.debugMode) {
10827         fprintf(debugFP, "feedMoves\n");
10828     }
10829     }
10830     for (i = backwardMostMove; i < upto; i++) {
10831         SendMoveToProgram(i, cps);
10832     }
10833 }
10834
10835
10836 int
10837 ResurrectChessProgram ()
10838 {
10839      /* The chess program may have exited.
10840         If so, restart it and feed it all the moves made so far. */
10841     static int doInit = 0;
10842
10843     if (appData.noChessProgram) return 1;
10844
10845     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10846         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10847         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10848         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10849     } else {
10850         if (first.pr != NoProc) return 1;
10851         StartChessProgram(&first);
10852     }
10853     InitChessProgram(&first, FALSE);
10854     FeedMovesToProgram(&first, currentMove);
10855
10856     if (!first.sendTime) {
10857         /* can't tell gnuchess what its clock should read,
10858            so we bow to its notion. */
10859         ResetClocks();
10860         timeRemaining[0][currentMove] = whiteTimeRemaining;
10861         timeRemaining[1][currentMove] = blackTimeRemaining;
10862     }
10863
10864     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10865                 appData.icsEngineAnalyze) && first.analysisSupport) {
10866       SendToProgram("analyze\n", &first);
10867       first.analyzing = TRUE;
10868     }
10869     return 1;
10870 }
10871
10872 /*
10873  * Button procedures
10874  */
10875 void
10876 Reset (int redraw, int init)
10877 {
10878     int i;
10879
10880     if (appData.debugMode) {
10881         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10882                 redraw, init, gameMode);
10883     }
10884     CleanupTail(); // [HGM] vari: delete any stored variations
10885     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10886     pausing = pauseExamInvalid = FALSE;
10887     startedFromSetupPosition = blackPlaysFirst = FALSE;
10888     firstMove = TRUE;
10889     whiteFlag = blackFlag = FALSE;
10890     userOfferedDraw = FALSE;
10891     hintRequested = bookRequested = FALSE;
10892     first.maybeThinking = FALSE;
10893     second.maybeThinking = FALSE;
10894     first.bookSuspend = FALSE; // [HGM] book
10895     second.bookSuspend = FALSE;
10896     thinkOutput[0] = NULLCHAR;
10897     lastHint[0] = NULLCHAR;
10898     ClearGameInfo(&gameInfo);
10899     gameInfo.variant = StringToVariant(appData.variant);
10900     ics_user_moved = ics_clock_paused = FALSE;
10901     ics_getting_history = H_FALSE;
10902     ics_gamenum = -1;
10903     white_holding[0] = black_holding[0] = NULLCHAR;
10904     ClearProgramStats();
10905     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10906
10907     ResetFrontEnd();
10908     ClearHighlights();
10909     flipView = appData.flipView;
10910     ClearPremoveHighlights();
10911     gotPremove = FALSE;
10912     alarmSounded = FALSE;
10913
10914     GameEnds(EndOfFile, NULL, GE_PLAYER);
10915     if(appData.serverMovesName != NULL) {
10916         /* [HGM] prepare to make moves file for broadcasting */
10917         clock_t t = clock();
10918         if(serverMoves != NULL) fclose(serverMoves);
10919         serverMoves = fopen(appData.serverMovesName, "r");
10920         if(serverMoves != NULL) {
10921             fclose(serverMoves);
10922             /* delay 15 sec before overwriting, so all clients can see end */
10923             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10924         }
10925         serverMoves = fopen(appData.serverMovesName, "w");
10926     }
10927
10928     ExitAnalyzeMode();
10929     gameMode = BeginningOfGame;
10930     ModeHighlight();
10931     if(appData.icsActive) gameInfo.variant = VariantNormal;
10932     currentMove = forwardMostMove = backwardMostMove = 0;
10933     MarkTargetSquares(1);
10934     InitPosition(redraw);
10935     for (i = 0; i < MAX_MOVES; i++) {
10936         if (commentList[i] != NULL) {
10937             free(commentList[i]);
10938             commentList[i] = NULL;
10939         }
10940     }
10941     ResetClocks();
10942     timeRemaining[0][0] = whiteTimeRemaining;
10943     timeRemaining[1][0] = blackTimeRemaining;
10944
10945     if (first.pr == NoProc) {
10946         StartChessProgram(&first);
10947     }
10948     if (init) {
10949             InitChessProgram(&first, startedFromSetupPosition);
10950     }
10951     DisplayTitle("");
10952     DisplayMessage("", "");
10953     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10954     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10955     ClearMap();        // [HGM] exclude: invalidate map
10956 }
10957
10958 void
10959 AutoPlayGameLoop ()
10960 {
10961     for (;;) {
10962         if (!AutoPlayOneMove())
10963           return;
10964         if (matchMode || appData.timeDelay == 0)
10965           continue;
10966         if (appData.timeDelay < 0)
10967           return;
10968         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
10969         break;
10970     }
10971 }
10972
10973
10974 int
10975 AutoPlayOneMove ()
10976 {
10977     int fromX, fromY, toX, toY;
10978
10979     if (appData.debugMode) {
10980       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10981     }
10982
10983     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10984       return FALSE;
10985
10986     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10987       pvInfoList[currentMove].depth = programStats.depth;
10988       pvInfoList[currentMove].score = programStats.score;
10989       pvInfoList[currentMove].time  = 0;
10990       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10991     }
10992
10993     if (currentMove >= forwardMostMove) {
10994       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10995 //      gameMode = EndOfGame;
10996 //      ModeHighlight();
10997
10998       /* [AS] Clear current move marker at the end of a game */
10999       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11000
11001       return FALSE;
11002     }
11003
11004     toX = moveList[currentMove][2] - AAA;
11005     toY = moveList[currentMove][3] - ONE;
11006
11007     if (moveList[currentMove][1] == '@') {
11008         if (appData.highlightLastMove) {
11009             SetHighlights(-1, -1, toX, toY);
11010         }
11011     } else {
11012         fromX = moveList[currentMove][0] - AAA;
11013         fromY = moveList[currentMove][1] - ONE;
11014
11015         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11016
11017         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11018
11019         if (appData.highlightLastMove) {
11020             SetHighlights(fromX, fromY, toX, toY);
11021         }
11022     }
11023     DisplayMove(currentMove);
11024     SendMoveToProgram(currentMove++, &first);
11025     DisplayBothClocks();
11026     DrawPosition(FALSE, boards[currentMove]);
11027     // [HGM] PV info: always display, routine tests if empty
11028     DisplayComment(currentMove - 1, commentList[currentMove]);
11029     return TRUE;
11030 }
11031
11032
11033 int
11034 LoadGameOneMove (ChessMove readAhead)
11035 {
11036     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11037     char promoChar = NULLCHAR;
11038     ChessMove moveType;
11039     char move[MSG_SIZ];
11040     char *p, *q;
11041
11042     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11043         gameMode != AnalyzeMode && gameMode != Training) {
11044         gameFileFP = NULL;
11045         return FALSE;
11046     }
11047
11048     yyboardindex = forwardMostMove;
11049     if (readAhead != EndOfFile) {
11050       moveType = readAhead;
11051     } else {
11052       if (gameFileFP == NULL)
11053           return FALSE;
11054       moveType = (ChessMove) Myylex();
11055     }
11056
11057     done = FALSE;
11058     switch (moveType) {
11059       case Comment:
11060         if (appData.debugMode)
11061           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11062         p = yy_text;
11063
11064         /* append the comment but don't display it */
11065         AppendComment(currentMove, p, FALSE);
11066         return TRUE;
11067
11068       case WhiteCapturesEnPassant:
11069       case BlackCapturesEnPassant:
11070       case WhitePromotion:
11071       case BlackPromotion:
11072       case WhiteNonPromotion:
11073       case BlackNonPromotion:
11074       case NormalMove:
11075       case WhiteKingSideCastle:
11076       case WhiteQueenSideCastle:
11077       case BlackKingSideCastle:
11078       case BlackQueenSideCastle:
11079       case WhiteKingSideCastleWild:
11080       case WhiteQueenSideCastleWild:
11081       case BlackKingSideCastleWild:
11082       case BlackQueenSideCastleWild:
11083       /* PUSH Fabien */
11084       case WhiteHSideCastleFR:
11085       case WhiteASideCastleFR:
11086       case BlackHSideCastleFR:
11087       case BlackASideCastleFR:
11088       /* POP Fabien */
11089         if (appData.debugMode)
11090           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11091         fromX = currentMoveString[0] - AAA;
11092         fromY = currentMoveString[1] - ONE;
11093         toX = currentMoveString[2] - AAA;
11094         toY = currentMoveString[3] - ONE;
11095         promoChar = currentMoveString[4];
11096         break;
11097
11098       case WhiteDrop:
11099       case BlackDrop:
11100         if (appData.debugMode)
11101           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11102         fromX = moveType == WhiteDrop ?
11103           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11104         (int) CharToPiece(ToLower(currentMoveString[0]));
11105         fromY = DROP_RANK;
11106         toX = currentMoveString[2] - AAA;
11107         toY = currentMoveString[3] - ONE;
11108         break;
11109
11110       case WhiteWins:
11111       case BlackWins:
11112       case GameIsDrawn:
11113       case GameUnfinished:
11114         if (appData.debugMode)
11115           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11116         p = strchr(yy_text, '{');
11117         if (p == NULL) p = strchr(yy_text, '(');
11118         if (p == NULL) {
11119             p = yy_text;
11120             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11121         } else {
11122             q = strchr(p, *p == '{' ? '}' : ')');
11123             if (q != NULL) *q = NULLCHAR;
11124             p++;
11125         }
11126         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11127         GameEnds(moveType, p, GE_FILE);
11128         done = TRUE;
11129         if (cmailMsgLoaded) {
11130             ClearHighlights();
11131             flipView = WhiteOnMove(currentMove);
11132             if (moveType == GameUnfinished) flipView = !flipView;
11133             if (appData.debugMode)
11134               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11135         }
11136         break;
11137
11138       case EndOfFile:
11139         if (appData.debugMode)
11140           fprintf(debugFP, "Parser hit end of file\n");
11141         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11142           case MT_NONE:
11143           case MT_CHECK:
11144             break;
11145           case MT_CHECKMATE:
11146           case MT_STAINMATE:
11147             if (WhiteOnMove(currentMove)) {
11148                 GameEnds(BlackWins, "Black mates", GE_FILE);
11149             } else {
11150                 GameEnds(WhiteWins, "White mates", GE_FILE);
11151             }
11152             break;
11153           case MT_STALEMATE:
11154             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11155             break;
11156         }
11157         done = TRUE;
11158         break;
11159
11160       case MoveNumberOne:
11161         if (lastLoadGameStart == GNUChessGame) {
11162             /* GNUChessGames have numbers, but they aren't move numbers */
11163             if (appData.debugMode)
11164               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11165                       yy_text, (int) moveType);
11166             return LoadGameOneMove(EndOfFile); /* tail recursion */
11167         }
11168         /* else fall thru */
11169
11170       case XBoardGame:
11171       case GNUChessGame:
11172       case PGNTag:
11173         /* Reached start of next game in file */
11174         if (appData.debugMode)
11175           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11176         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11177           case MT_NONE:
11178           case MT_CHECK:
11179             break;
11180           case MT_CHECKMATE:
11181           case MT_STAINMATE:
11182             if (WhiteOnMove(currentMove)) {
11183                 GameEnds(BlackWins, "Black mates", GE_FILE);
11184             } else {
11185                 GameEnds(WhiteWins, "White mates", GE_FILE);
11186             }
11187             break;
11188           case MT_STALEMATE:
11189             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11190             break;
11191         }
11192         done = TRUE;
11193         break;
11194
11195       case PositionDiagram:     /* should not happen; ignore */
11196       case ElapsedTime:         /* ignore */
11197       case NAG:                 /* ignore */
11198         if (appData.debugMode)
11199           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11200                   yy_text, (int) moveType);
11201         return LoadGameOneMove(EndOfFile); /* tail recursion */
11202
11203       case IllegalMove:
11204         if (appData.testLegality) {
11205             if (appData.debugMode)
11206               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11207             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11208                     (forwardMostMove / 2) + 1,
11209                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11210             DisplayError(move, 0);
11211             done = TRUE;
11212         } else {
11213             if (appData.debugMode)
11214               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11215                       yy_text, currentMoveString);
11216             fromX = currentMoveString[0] - AAA;
11217             fromY = currentMoveString[1] - ONE;
11218             toX = currentMoveString[2] - AAA;
11219             toY = currentMoveString[3] - ONE;
11220             promoChar = currentMoveString[4];
11221         }
11222         break;
11223
11224       case AmbiguousMove:
11225         if (appData.debugMode)
11226           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11227         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11228                 (forwardMostMove / 2) + 1,
11229                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11230         DisplayError(move, 0);
11231         done = TRUE;
11232         break;
11233
11234       default:
11235       case ImpossibleMove:
11236         if (appData.debugMode)
11237           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11238         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11239                 (forwardMostMove / 2) + 1,
11240                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11241         DisplayError(move, 0);
11242         done = TRUE;
11243         break;
11244     }
11245
11246     if (done) {
11247         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11248             DrawPosition(FALSE, boards[currentMove]);
11249             DisplayBothClocks();
11250             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11251               DisplayComment(currentMove - 1, commentList[currentMove]);
11252         }
11253         (void) StopLoadGameTimer();
11254         gameFileFP = NULL;
11255         cmailOldMove = forwardMostMove;
11256         return FALSE;
11257     } else {
11258         /* currentMoveString is set as a side-effect of yylex */
11259
11260         thinkOutput[0] = NULLCHAR;
11261         MakeMove(fromX, fromY, toX, toY, promoChar);
11262         currentMove = forwardMostMove;
11263         return TRUE;
11264     }
11265 }
11266
11267 /* Load the nth game from the given file */
11268 int
11269 LoadGameFromFile (char *filename, int n, char *title, int useList)
11270 {
11271     FILE *f;
11272     char buf[MSG_SIZ];
11273
11274     if (strcmp(filename, "-") == 0) {
11275         f = stdin;
11276         title = "stdin";
11277     } else {
11278         f = fopen(filename, "rb");
11279         if (f == NULL) {
11280           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11281             DisplayError(buf, errno);
11282             return FALSE;
11283         }
11284     }
11285     if (fseek(f, 0, 0) == -1) {
11286         /* f is not seekable; probably a pipe */
11287         useList = FALSE;
11288     }
11289     if (useList && n == 0) {
11290         int error = GameListBuild(f);
11291         if (error) {
11292             DisplayError(_("Cannot build game list"), error);
11293         } else if (!ListEmpty(&gameList) &&
11294                    ((ListGame *) gameList.tailPred)->number > 1) {
11295             GameListPopUp(f, title);
11296             return TRUE;
11297         }
11298         GameListDestroy();
11299         n = 1;
11300     }
11301     if (n == 0) n = 1;
11302     return LoadGame(f, n, title, FALSE);
11303 }
11304
11305
11306 void
11307 MakeRegisteredMove ()
11308 {
11309     int fromX, fromY, toX, toY;
11310     char promoChar;
11311     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11312         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11313           case CMAIL_MOVE:
11314           case CMAIL_DRAW:
11315             if (appData.debugMode)
11316               fprintf(debugFP, "Restoring %s for game %d\n",
11317                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11318
11319             thinkOutput[0] = NULLCHAR;
11320             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11321             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11322             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11323             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11324             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11325             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11326             MakeMove(fromX, fromY, toX, toY, promoChar);
11327             ShowMove(fromX, fromY, toX, toY);
11328
11329             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11330               case MT_NONE:
11331               case MT_CHECK:
11332                 break;
11333
11334               case MT_CHECKMATE:
11335               case MT_STAINMATE:
11336                 if (WhiteOnMove(currentMove)) {
11337                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11338                 } else {
11339                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11340                 }
11341                 break;
11342
11343               case MT_STALEMATE:
11344                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11345                 break;
11346             }
11347
11348             break;
11349
11350           case CMAIL_RESIGN:
11351             if (WhiteOnMove(currentMove)) {
11352                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11353             } else {
11354                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11355             }
11356             break;
11357
11358           case CMAIL_ACCEPT:
11359             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11360             break;
11361
11362           default:
11363             break;
11364         }
11365     }
11366
11367     return;
11368 }
11369
11370 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11371 int
11372 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11373 {
11374     int retVal;
11375
11376     if (gameNumber > nCmailGames) {
11377         DisplayError(_("No more games in this message"), 0);
11378         return FALSE;
11379     }
11380     if (f == lastLoadGameFP) {
11381         int offset = gameNumber - lastLoadGameNumber;
11382         if (offset == 0) {
11383             cmailMsg[0] = NULLCHAR;
11384             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11385                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11386                 nCmailMovesRegistered--;
11387             }
11388             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11389             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11390                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11391             }
11392         } else {
11393             if (! RegisterMove()) return FALSE;
11394         }
11395     }
11396
11397     retVal = LoadGame(f, gameNumber, title, useList);
11398
11399     /* Make move registered during previous look at this game, if any */
11400     MakeRegisteredMove();
11401
11402     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11403         commentList[currentMove]
11404           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11405         DisplayComment(currentMove - 1, commentList[currentMove]);
11406     }
11407
11408     return retVal;
11409 }
11410
11411 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11412 int
11413 ReloadGame (int offset)
11414 {
11415     int gameNumber = lastLoadGameNumber + offset;
11416     if (lastLoadGameFP == NULL) {
11417         DisplayError(_("No game has been loaded yet"), 0);
11418         return FALSE;
11419     }
11420     if (gameNumber <= 0) {
11421         DisplayError(_("Can't back up any further"), 0);
11422         return FALSE;
11423     }
11424     if (cmailMsgLoaded) {
11425         return CmailLoadGame(lastLoadGameFP, gameNumber,
11426                              lastLoadGameTitle, lastLoadGameUseList);
11427     } else {
11428         return LoadGame(lastLoadGameFP, gameNumber,
11429                         lastLoadGameTitle, lastLoadGameUseList);
11430     }
11431 }
11432
11433 int keys[EmptySquare+1];
11434
11435 int
11436 PositionMatches (Board b1, Board b2)
11437 {
11438     int r, f, sum=0;
11439     switch(appData.searchMode) {
11440         case 1: return CompareWithRights(b1, b2);
11441         case 2:
11442             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11443                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11444             }
11445             return TRUE;
11446         case 3:
11447             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11448               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11449                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11450             }
11451             return sum==0;
11452         case 4:
11453             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11454                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11455             }
11456             return sum==0;
11457     }
11458     return TRUE;
11459 }
11460
11461 #define Q_PROMO  4
11462 #define Q_EP     3
11463 #define Q_BCASTL 2
11464 #define Q_WCASTL 1
11465
11466 int pieceList[256], quickBoard[256];
11467 ChessSquare pieceType[256] = { EmptySquare };
11468 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11469 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11470 int soughtTotal, turn;
11471 Boolean epOK, flipSearch;
11472
11473 typedef struct {
11474     unsigned char piece, to;
11475 } Move;
11476
11477 #define DSIZE (250000)
11478
11479 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11480 Move *moveDatabase = initialSpace;
11481 unsigned int movePtr, dataSize = DSIZE;
11482
11483 int
11484 MakePieceList (Board board, int *counts)
11485 {
11486     int r, f, n=Q_PROMO, total=0;
11487     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11488     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11489         int sq = f + (r<<4);
11490         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11491             quickBoard[sq] = ++n;
11492             pieceList[n] = sq;
11493             pieceType[n] = board[r][f];
11494             counts[board[r][f]]++;
11495             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11496             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11497             total++;
11498         }
11499     }
11500     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11501     return total;
11502 }
11503
11504 void
11505 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11506 {
11507     int sq = fromX + (fromY<<4);
11508     int piece = quickBoard[sq];
11509     quickBoard[sq] = 0;
11510     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11511     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11512         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11513         moveDatabase[movePtr++].piece = Q_WCASTL;
11514         quickBoard[sq] = piece;
11515         piece = quickBoard[from]; quickBoard[from] = 0;
11516         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11517     } else
11518     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11519         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11520         moveDatabase[movePtr++].piece = Q_BCASTL;
11521         quickBoard[sq] = piece;
11522         piece = quickBoard[from]; quickBoard[from] = 0;
11523         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11524     } else
11525     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11526         quickBoard[(fromY<<4)+toX] = 0;
11527         moveDatabase[movePtr].piece = Q_EP;
11528         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11529         moveDatabase[movePtr].to = sq;
11530     } else
11531     if(promoPiece != pieceType[piece]) {
11532         moveDatabase[movePtr++].piece = Q_PROMO;
11533         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11534     }
11535     moveDatabase[movePtr].piece = piece;
11536     quickBoard[sq] = piece;
11537     movePtr++;
11538 }
11539
11540 int
11541 PackGame (Board board)
11542 {
11543     Move *newSpace = NULL;
11544     moveDatabase[movePtr].piece = 0; // terminate previous game
11545     if(movePtr > dataSize) {
11546         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11547         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11548         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11549         if(newSpace) {
11550             int i;
11551             Move *p = moveDatabase, *q = newSpace;
11552             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11553             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11554             moveDatabase = newSpace;
11555         } else { // calloc failed, we must be out of memory. Too bad...
11556             dataSize = 0; // prevent calloc events for all subsequent games
11557             return 0;     // and signal this one isn't cached
11558         }
11559     }
11560     movePtr++;
11561     MakePieceList(board, counts);
11562     return movePtr;
11563 }
11564
11565 int
11566 QuickCompare (Board board, int *minCounts, int *maxCounts)
11567 {   // compare according to search mode
11568     int r, f;
11569     switch(appData.searchMode)
11570     {
11571       case 1: // exact position match
11572         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11573         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11574             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11575         }
11576         break;
11577       case 2: // can have extra material on empty squares
11578         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11579             if(board[r][f] == EmptySquare) continue;
11580             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11581         }
11582         break;
11583       case 3: // material with exact Pawn structure
11584         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11585             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11586             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11587         } // fall through to material comparison
11588       case 4: // exact material
11589         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11590         break;
11591       case 6: // material range with given imbalance
11592         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11593         // fall through to range comparison
11594       case 5: // material range
11595         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11596     }
11597     return TRUE;
11598 }
11599
11600 int
11601 QuickScan (Board board, Move *move)
11602 {   // reconstruct game,and compare all positions in it
11603     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11604     do {
11605         int piece = move->piece;
11606         int to = move->to, from = pieceList[piece];
11607         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11608           if(!piece) return -1;
11609           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11610             piece = (++move)->piece;
11611             from = pieceList[piece];
11612             counts[pieceType[piece]]--;
11613             pieceType[piece] = (ChessSquare) move->to;
11614             counts[move->to]++;
11615           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11616             counts[pieceType[quickBoard[to]]]--;
11617             quickBoard[to] = 0; total--;
11618             move++;
11619             continue;
11620           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11621             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11622             from  = pieceList[piece]; // so this must be King
11623             quickBoard[from] = 0;
11624             pieceList[piece] = to;
11625             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11626             quickBoard[from] = 0; // rook
11627             quickBoard[to] = piece;
11628             to = move->to; piece = move->piece;
11629             goto aftercastle;
11630           }
11631         }
11632         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11633         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11634         quickBoard[from] = 0;
11635       aftercastle:
11636         quickBoard[to] = piece;
11637         pieceList[piece] = to;
11638         cnt++; turn ^= 3;
11639         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11640            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11641            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11642                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11643           ) {
11644             static int lastCounts[EmptySquare+1];
11645             int i;
11646             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11647             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11648         } else stretch = 0;
11649         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11650         move++;
11651     } while(1);
11652 }
11653
11654 void
11655 InitSearch ()
11656 {
11657     int r, f;
11658     flipSearch = FALSE;
11659     CopyBoard(soughtBoard, boards[currentMove]);
11660     soughtTotal = MakePieceList(soughtBoard, maxSought);
11661     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11662     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11663     CopyBoard(reverseBoard, boards[currentMove]);
11664     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11665         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11666         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11667         reverseBoard[r][f] = piece;
11668     }
11669     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11670     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11671     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11672                  || (boards[currentMove][CASTLING][2] == NoRights || 
11673                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11674                  && (boards[currentMove][CASTLING][5] == NoRights || 
11675                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11676       ) {
11677         flipSearch = TRUE;
11678         CopyBoard(flipBoard, soughtBoard);
11679         CopyBoard(rotateBoard, reverseBoard);
11680         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11681             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11682             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11683         }
11684     }
11685     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11686     if(appData.searchMode >= 5) {
11687         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11688         MakePieceList(soughtBoard, minSought);
11689         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11690     }
11691     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11692         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11693 }
11694
11695 GameInfo dummyInfo;
11696
11697 int
11698 GameContainsPosition (FILE *f, ListGame *lg)
11699 {
11700     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11701     int fromX, fromY, toX, toY;
11702     char promoChar;
11703     static int initDone=FALSE;
11704
11705     // weed out games based on numerical tag comparison
11706     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11707     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11708     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11709     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11710     if(!initDone) {
11711         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11712         initDone = TRUE;
11713     }
11714     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11715     else CopyBoard(boards[scratch], initialPosition); // default start position
11716     if(lg->moves) {
11717         turn = btm + 1;
11718         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11719         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11720     }
11721     if(btm) plyNr++;
11722     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11723     fseek(f, lg->offset, 0);
11724     yynewfile(f);
11725     while(1) {
11726         yyboardindex = scratch;
11727         quickFlag = plyNr+1;
11728         next = Myylex();
11729         quickFlag = 0;
11730         switch(next) {
11731             case PGNTag:
11732                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11733             default:
11734                 continue;
11735
11736             case XBoardGame:
11737             case GNUChessGame:
11738                 if(plyNr) return -1; // after we have seen moves, this is for new game
11739               continue;
11740
11741             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11742             case ImpossibleMove:
11743             case WhiteWins: // game ends here with these four
11744             case BlackWins:
11745             case GameIsDrawn:
11746             case GameUnfinished:
11747                 return -1;
11748
11749             case IllegalMove:
11750                 if(appData.testLegality) return -1;
11751             case WhiteCapturesEnPassant:
11752             case BlackCapturesEnPassant:
11753             case WhitePromotion:
11754             case BlackPromotion:
11755             case WhiteNonPromotion:
11756             case BlackNonPromotion:
11757             case NormalMove:
11758             case WhiteKingSideCastle:
11759             case WhiteQueenSideCastle:
11760             case BlackKingSideCastle:
11761             case BlackQueenSideCastle:
11762             case WhiteKingSideCastleWild:
11763             case WhiteQueenSideCastleWild:
11764             case BlackKingSideCastleWild:
11765             case BlackQueenSideCastleWild:
11766             case WhiteHSideCastleFR:
11767             case WhiteASideCastleFR:
11768             case BlackHSideCastleFR:
11769             case BlackASideCastleFR:
11770                 fromX = currentMoveString[0] - AAA;
11771                 fromY = currentMoveString[1] - ONE;
11772                 toX = currentMoveString[2] - AAA;
11773                 toY = currentMoveString[3] - ONE;
11774                 promoChar = currentMoveString[4];
11775                 break;
11776             case WhiteDrop:
11777             case BlackDrop:
11778                 fromX = next == WhiteDrop ?
11779                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11780                   (int) CharToPiece(ToLower(currentMoveString[0]));
11781                 fromY = DROP_RANK;
11782                 toX = currentMoveString[2] - AAA;
11783                 toY = currentMoveString[3] - ONE;
11784                 promoChar = 0;
11785                 break;
11786         }
11787         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11788         plyNr++;
11789         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11790         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11791         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11792         if(appData.findMirror) {
11793             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11794             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11795         }
11796     }
11797 }
11798
11799 /* Load the nth game from open file f */
11800 int
11801 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11802 {
11803     ChessMove cm;
11804     char buf[MSG_SIZ];
11805     int gn = gameNumber;
11806     ListGame *lg = NULL;
11807     int numPGNTags = 0;
11808     int err, pos = -1;
11809     GameMode oldGameMode;
11810     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11811
11812     if (appData.debugMode)
11813         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11814
11815     if (gameMode == Training )
11816         SetTrainingModeOff();
11817
11818     oldGameMode = gameMode;
11819     if (gameMode != BeginningOfGame) {
11820       Reset(FALSE, TRUE);
11821     }
11822
11823     gameFileFP = f;
11824     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11825         fclose(lastLoadGameFP);
11826     }
11827
11828     if (useList) {
11829         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11830
11831         if (lg) {
11832             fseek(f, lg->offset, 0);
11833             GameListHighlight(gameNumber);
11834             pos = lg->position;
11835             gn = 1;
11836         }
11837         else {
11838             DisplayError(_("Game number out of range"), 0);
11839             return FALSE;
11840         }
11841     } else {
11842         GameListDestroy();
11843         if (fseek(f, 0, 0) == -1) {
11844             if (f == lastLoadGameFP ?
11845                 gameNumber == lastLoadGameNumber + 1 :
11846                 gameNumber == 1) {
11847                 gn = 1;
11848             } else {
11849                 DisplayError(_("Can't seek on game file"), 0);
11850                 return FALSE;
11851             }
11852         }
11853     }
11854     lastLoadGameFP = f;
11855     lastLoadGameNumber = gameNumber;
11856     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11857     lastLoadGameUseList = useList;
11858
11859     yynewfile(f);
11860
11861     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11862       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11863                 lg->gameInfo.black);
11864             DisplayTitle(buf);
11865     } else if (*title != NULLCHAR) {
11866         if (gameNumber > 1) {
11867           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11868             DisplayTitle(buf);
11869         } else {
11870             DisplayTitle(title);
11871         }
11872     }
11873
11874     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11875         gameMode = PlayFromGameFile;
11876         ModeHighlight();
11877     }
11878
11879     currentMove = forwardMostMove = backwardMostMove = 0;
11880     CopyBoard(boards[0], initialPosition);
11881     StopClocks();
11882
11883     /*
11884      * Skip the first gn-1 games in the file.
11885      * Also skip over anything that precedes an identifiable
11886      * start of game marker, to avoid being confused by
11887      * garbage at the start of the file.  Currently
11888      * recognized start of game markers are the move number "1",
11889      * the pattern "gnuchess .* game", the pattern
11890      * "^[#;%] [^ ]* game file", and a PGN tag block.
11891      * A game that starts with one of the latter two patterns
11892      * will also have a move number 1, possibly
11893      * following a position diagram.
11894      * 5-4-02: Let's try being more lenient and allowing a game to
11895      * start with an unnumbered move.  Does that break anything?
11896      */
11897     cm = lastLoadGameStart = EndOfFile;
11898     while (gn > 0) {
11899         yyboardindex = forwardMostMove;
11900         cm = (ChessMove) Myylex();
11901         switch (cm) {
11902           case EndOfFile:
11903             if (cmailMsgLoaded) {
11904                 nCmailGames = CMAIL_MAX_GAMES - gn;
11905             } else {
11906                 Reset(TRUE, TRUE);
11907                 DisplayError(_("Game not found in file"), 0);
11908             }
11909             return FALSE;
11910
11911           case GNUChessGame:
11912           case XBoardGame:
11913             gn--;
11914             lastLoadGameStart = cm;
11915             break;
11916
11917           case MoveNumberOne:
11918             switch (lastLoadGameStart) {
11919               case GNUChessGame:
11920               case XBoardGame:
11921               case PGNTag:
11922                 break;
11923               case MoveNumberOne:
11924               case EndOfFile:
11925                 gn--;           /* count this game */
11926                 lastLoadGameStart = cm;
11927                 break;
11928               default:
11929                 /* impossible */
11930                 break;
11931             }
11932             break;
11933
11934           case PGNTag:
11935             switch (lastLoadGameStart) {
11936               case GNUChessGame:
11937               case PGNTag:
11938               case MoveNumberOne:
11939               case EndOfFile:
11940                 gn--;           /* count this game */
11941                 lastLoadGameStart = cm;
11942                 break;
11943               case XBoardGame:
11944                 lastLoadGameStart = cm; /* game counted already */
11945                 break;
11946               default:
11947                 /* impossible */
11948                 break;
11949             }
11950             if (gn > 0) {
11951                 do {
11952                     yyboardindex = forwardMostMove;
11953                     cm = (ChessMove) Myylex();
11954                 } while (cm == PGNTag || cm == Comment);
11955             }
11956             break;
11957
11958           case WhiteWins:
11959           case BlackWins:
11960           case GameIsDrawn:
11961             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11962                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11963                     != CMAIL_OLD_RESULT) {
11964                     nCmailResults ++ ;
11965                     cmailResult[  CMAIL_MAX_GAMES
11966                                 - gn - 1] = CMAIL_OLD_RESULT;
11967                 }
11968             }
11969             break;
11970
11971           case NormalMove:
11972             /* Only a NormalMove can be at the start of a game
11973              * without a position diagram. */
11974             if (lastLoadGameStart == EndOfFile ) {
11975               gn--;
11976               lastLoadGameStart = MoveNumberOne;
11977             }
11978             break;
11979
11980           default:
11981             break;
11982         }
11983     }
11984
11985     if (appData.debugMode)
11986       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11987
11988     if (cm == XBoardGame) {
11989         /* Skip any header junk before position diagram and/or move 1 */
11990         for (;;) {
11991             yyboardindex = forwardMostMove;
11992             cm = (ChessMove) Myylex();
11993
11994             if (cm == EndOfFile ||
11995                 cm == GNUChessGame || cm == XBoardGame) {
11996                 /* Empty game; pretend end-of-file and handle later */
11997                 cm = EndOfFile;
11998                 break;
11999             }
12000
12001             if (cm == MoveNumberOne || cm == PositionDiagram ||
12002                 cm == PGNTag || cm == Comment)
12003               break;
12004         }
12005     } else if (cm == GNUChessGame) {
12006         if (gameInfo.event != NULL) {
12007             free(gameInfo.event);
12008         }
12009         gameInfo.event = StrSave(yy_text);
12010     }
12011
12012     startedFromSetupPosition = FALSE;
12013     while (cm == PGNTag) {
12014         if (appData.debugMode)
12015           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12016         err = ParsePGNTag(yy_text, &gameInfo);
12017         if (!err) numPGNTags++;
12018
12019         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12020         if(gameInfo.variant != oldVariant) {
12021             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12022             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12023             InitPosition(TRUE);
12024             oldVariant = gameInfo.variant;
12025             if (appData.debugMode)
12026               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12027         }
12028
12029
12030         if (gameInfo.fen != NULL) {
12031           Board initial_position;
12032           startedFromSetupPosition = TRUE;
12033           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12034             Reset(TRUE, TRUE);
12035             DisplayError(_("Bad FEN position in file"), 0);
12036             return FALSE;
12037           }
12038           CopyBoard(boards[0], initial_position);
12039           if (blackPlaysFirst) {
12040             currentMove = forwardMostMove = backwardMostMove = 1;
12041             CopyBoard(boards[1], initial_position);
12042             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12043             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12044             timeRemaining[0][1] = whiteTimeRemaining;
12045             timeRemaining[1][1] = blackTimeRemaining;
12046             if (commentList[0] != NULL) {
12047               commentList[1] = commentList[0];
12048               commentList[0] = NULL;
12049             }
12050           } else {
12051             currentMove = forwardMostMove = backwardMostMove = 0;
12052           }
12053           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12054           {   int i;
12055               initialRulePlies = FENrulePlies;
12056               for( i=0; i< nrCastlingRights; i++ )
12057                   initialRights[i] = initial_position[CASTLING][i];
12058           }
12059           yyboardindex = forwardMostMove;
12060           free(gameInfo.fen);
12061           gameInfo.fen = NULL;
12062         }
12063
12064         yyboardindex = forwardMostMove;
12065         cm = (ChessMove) Myylex();
12066
12067         /* Handle comments interspersed among the tags */
12068         while (cm == Comment) {
12069             char *p;
12070             if (appData.debugMode)
12071               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12072             p = yy_text;
12073             AppendComment(currentMove, p, FALSE);
12074             yyboardindex = forwardMostMove;
12075             cm = (ChessMove) Myylex();
12076         }
12077     }
12078
12079     /* don't rely on existence of Event tag since if game was
12080      * pasted from clipboard the Event tag may not exist
12081      */
12082     if (numPGNTags > 0){
12083         char *tags;
12084         if (gameInfo.variant == VariantNormal) {
12085           VariantClass v = StringToVariant(gameInfo.event);
12086           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12087           if(v < VariantShogi) gameInfo.variant = v;
12088         }
12089         if (!matchMode) {
12090           if( appData.autoDisplayTags ) {
12091             tags = PGNTags(&gameInfo);
12092             TagsPopUp(tags, CmailMsg());
12093             free(tags);
12094           }
12095         }
12096     } else {
12097         /* Make something up, but don't display it now */
12098         SetGameInfo();
12099         TagsPopDown();
12100     }
12101
12102     if (cm == PositionDiagram) {
12103         int i, j;
12104         char *p;
12105         Board initial_position;
12106
12107         if (appData.debugMode)
12108           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12109
12110         if (!startedFromSetupPosition) {
12111             p = yy_text;
12112             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12113               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12114                 switch (*p) {
12115                   case '{':
12116                   case '[':
12117                   case '-':
12118                   case ' ':
12119                   case '\t':
12120                   case '\n':
12121                   case '\r':
12122                     break;
12123                   default:
12124                     initial_position[i][j++] = CharToPiece(*p);
12125                     break;
12126                 }
12127             while (*p == ' ' || *p == '\t' ||
12128                    *p == '\n' || *p == '\r') p++;
12129
12130             if (strncmp(p, "black", strlen("black"))==0)
12131               blackPlaysFirst = TRUE;
12132             else
12133               blackPlaysFirst = FALSE;
12134             startedFromSetupPosition = TRUE;
12135
12136             CopyBoard(boards[0], initial_position);
12137             if (blackPlaysFirst) {
12138                 currentMove = forwardMostMove = backwardMostMove = 1;
12139                 CopyBoard(boards[1], initial_position);
12140                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12141                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12142                 timeRemaining[0][1] = whiteTimeRemaining;
12143                 timeRemaining[1][1] = blackTimeRemaining;
12144                 if (commentList[0] != NULL) {
12145                     commentList[1] = commentList[0];
12146                     commentList[0] = NULL;
12147                 }
12148             } else {
12149                 currentMove = forwardMostMove = backwardMostMove = 0;
12150             }
12151         }
12152         yyboardindex = forwardMostMove;
12153         cm = (ChessMove) Myylex();
12154     }
12155
12156     if (first.pr == NoProc) {
12157         StartChessProgram(&first);
12158     }
12159     InitChessProgram(&first, FALSE);
12160     SendToProgram("force\n", &first);
12161     if (startedFromSetupPosition) {
12162         SendBoard(&first, forwardMostMove);
12163     if (appData.debugMode) {
12164         fprintf(debugFP, "Load Game\n");
12165     }
12166         DisplayBothClocks();
12167     }
12168
12169     /* [HGM] server: flag to write setup moves in broadcast file as one */
12170     loadFlag = appData.suppressLoadMoves;
12171
12172     while (cm == Comment) {
12173         char *p;
12174         if (appData.debugMode)
12175           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12176         p = yy_text;
12177         AppendComment(currentMove, p, FALSE);
12178         yyboardindex = forwardMostMove;
12179         cm = (ChessMove) Myylex();
12180     }
12181
12182     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12183         cm == WhiteWins || cm == BlackWins ||
12184         cm == GameIsDrawn || cm == GameUnfinished) {
12185         DisplayMessage("", _("No moves in game"));
12186         if (cmailMsgLoaded) {
12187             if (appData.debugMode)
12188               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12189             ClearHighlights();
12190             flipView = FALSE;
12191         }
12192         DrawPosition(FALSE, boards[currentMove]);
12193         DisplayBothClocks();
12194         gameMode = EditGame;
12195         ModeHighlight();
12196         gameFileFP = NULL;
12197         cmailOldMove = 0;
12198         return TRUE;
12199     }
12200
12201     // [HGM] PV info: routine tests if comment empty
12202     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12203         DisplayComment(currentMove - 1, commentList[currentMove]);
12204     }
12205     if (!matchMode && appData.timeDelay != 0)
12206       DrawPosition(FALSE, boards[currentMove]);
12207
12208     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12209       programStats.ok_to_send = 1;
12210     }
12211
12212     /* if the first token after the PGN tags is a move
12213      * and not move number 1, retrieve it from the parser
12214      */
12215     if (cm != MoveNumberOne)
12216         LoadGameOneMove(cm);
12217
12218     /* load the remaining moves from the file */
12219     while (LoadGameOneMove(EndOfFile)) {
12220       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12221       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12222     }
12223
12224     /* rewind to the start of the game */
12225     currentMove = backwardMostMove;
12226
12227     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12228
12229     if (oldGameMode == AnalyzeFile ||
12230         oldGameMode == AnalyzeMode) {
12231       AnalyzeFileEvent();
12232     }
12233
12234     if (!matchMode && pos > 0) {
12235         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12236     } else
12237     if (matchMode || appData.timeDelay == 0) {
12238       ToEndEvent();
12239     } else if (appData.timeDelay > 0) {
12240       AutoPlayGameLoop();
12241     }
12242
12243     if (appData.debugMode)
12244         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12245
12246     loadFlag = 0; /* [HGM] true game starts */
12247     return TRUE;
12248 }
12249
12250 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12251 int
12252 ReloadPosition (int offset)
12253 {
12254     int positionNumber = lastLoadPositionNumber + offset;
12255     if (lastLoadPositionFP == NULL) {
12256         DisplayError(_("No position has been loaded yet"), 0);
12257         return FALSE;
12258     }
12259     if (positionNumber <= 0) {
12260         DisplayError(_("Can't back up any further"), 0);
12261         return FALSE;
12262     }
12263     return LoadPosition(lastLoadPositionFP, positionNumber,
12264                         lastLoadPositionTitle);
12265 }
12266
12267 /* Load the nth position from the given file */
12268 int
12269 LoadPositionFromFile (char *filename, int n, char *title)
12270 {
12271     FILE *f;
12272     char buf[MSG_SIZ];
12273
12274     if (strcmp(filename, "-") == 0) {
12275         return LoadPosition(stdin, n, "stdin");
12276     } else {
12277         f = fopen(filename, "rb");
12278         if (f == NULL) {
12279             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12280             DisplayError(buf, errno);
12281             return FALSE;
12282         } else {
12283             return LoadPosition(f, n, title);
12284         }
12285     }
12286 }
12287
12288 /* Load the nth position from the given open file, and close it */
12289 int
12290 LoadPosition (FILE *f, int positionNumber, char *title)
12291 {
12292     char *p, line[MSG_SIZ];
12293     Board initial_position;
12294     int i, j, fenMode, pn;
12295
12296     if (gameMode == Training )
12297         SetTrainingModeOff();
12298
12299     if (gameMode != BeginningOfGame) {
12300         Reset(FALSE, TRUE);
12301     }
12302     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12303         fclose(lastLoadPositionFP);
12304     }
12305     if (positionNumber == 0) positionNumber = 1;
12306     lastLoadPositionFP = f;
12307     lastLoadPositionNumber = positionNumber;
12308     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12309     if (first.pr == NoProc && !appData.noChessProgram) {
12310       StartChessProgram(&first);
12311       InitChessProgram(&first, FALSE);
12312     }
12313     pn = positionNumber;
12314     if (positionNumber < 0) {
12315         /* Negative position number means to seek to that byte offset */
12316         if (fseek(f, -positionNumber, 0) == -1) {
12317             DisplayError(_("Can't seek on position file"), 0);
12318             return FALSE;
12319         };
12320         pn = 1;
12321     } else {
12322         if (fseek(f, 0, 0) == -1) {
12323             if (f == lastLoadPositionFP ?
12324                 positionNumber == lastLoadPositionNumber + 1 :
12325                 positionNumber == 1) {
12326                 pn = 1;
12327             } else {
12328                 DisplayError(_("Can't seek on position file"), 0);
12329                 return FALSE;
12330             }
12331         }
12332     }
12333     /* See if this file is FEN or old-style xboard */
12334     if (fgets(line, MSG_SIZ, f) == NULL) {
12335         DisplayError(_("Position not found in file"), 0);
12336         return FALSE;
12337     }
12338     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12339     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12340
12341     if (pn >= 2) {
12342         if (fenMode || line[0] == '#') pn--;
12343         while (pn > 0) {
12344             /* skip positions before number pn */
12345             if (fgets(line, MSG_SIZ, f) == NULL) {
12346                 Reset(TRUE, TRUE);
12347                 DisplayError(_("Position not found in file"), 0);
12348                 return FALSE;
12349             }
12350             if (fenMode || line[0] == '#') pn--;
12351         }
12352     }
12353
12354     if (fenMode) {
12355         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12356             DisplayError(_("Bad FEN position in file"), 0);
12357             return FALSE;
12358         }
12359     } else {
12360         (void) fgets(line, MSG_SIZ, f);
12361         (void) fgets(line, MSG_SIZ, f);
12362
12363         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12364             (void) fgets(line, MSG_SIZ, f);
12365             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12366                 if (*p == ' ')
12367                   continue;
12368                 initial_position[i][j++] = CharToPiece(*p);
12369             }
12370         }
12371
12372         blackPlaysFirst = FALSE;
12373         if (!feof(f)) {
12374             (void) fgets(line, MSG_SIZ, f);
12375             if (strncmp(line, "black", strlen("black"))==0)
12376               blackPlaysFirst = TRUE;
12377         }
12378     }
12379     startedFromSetupPosition = TRUE;
12380
12381     CopyBoard(boards[0], initial_position);
12382     if (blackPlaysFirst) {
12383         currentMove = forwardMostMove = backwardMostMove = 1;
12384         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12385         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12386         CopyBoard(boards[1], initial_position);
12387         DisplayMessage("", _("Black to play"));
12388     } else {
12389         currentMove = forwardMostMove = backwardMostMove = 0;
12390         DisplayMessage("", _("White to play"));
12391     }
12392     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12393     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12394         SendToProgram("force\n", &first);
12395         SendBoard(&first, forwardMostMove);
12396     }
12397     if (appData.debugMode) {
12398 int i, j;
12399   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12400   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12401         fprintf(debugFP, "Load Position\n");
12402     }
12403
12404     if (positionNumber > 1) {
12405       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12406         DisplayTitle(line);
12407     } else {
12408         DisplayTitle(title);
12409     }
12410     gameMode = EditGame;
12411     ModeHighlight();
12412     ResetClocks();
12413     timeRemaining[0][1] = whiteTimeRemaining;
12414     timeRemaining[1][1] = blackTimeRemaining;
12415     DrawPosition(FALSE, boards[currentMove]);
12416
12417     return TRUE;
12418 }
12419
12420
12421 void
12422 CopyPlayerNameIntoFileName (char **dest, char *src)
12423 {
12424     while (*src != NULLCHAR && *src != ',') {
12425         if (*src == ' ') {
12426             *(*dest)++ = '_';
12427             src++;
12428         } else {
12429             *(*dest)++ = *src++;
12430         }
12431     }
12432 }
12433
12434 char *
12435 DefaultFileName (char *ext)
12436 {
12437     static char def[MSG_SIZ];
12438     char *p;
12439
12440     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12441         p = def;
12442         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12443         *p++ = '-';
12444         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12445         *p++ = '.';
12446         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12447     } else {
12448         def[0] = NULLCHAR;
12449     }
12450     return def;
12451 }
12452
12453 /* Save the current game to the given file */
12454 int
12455 SaveGameToFile (char *filename, int append)
12456 {
12457     FILE *f;
12458     char buf[MSG_SIZ];
12459     int result, i, t,tot=0;
12460
12461     if (strcmp(filename, "-") == 0) {
12462         return SaveGame(stdout, 0, NULL);
12463     } else {
12464         for(i=0; i<10; i++) { // upto 10 tries
12465              f = fopen(filename, append ? "a" : "w");
12466              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12467              if(f || errno != 13) break;
12468              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12469              tot += t;
12470         }
12471         if (f == NULL) {
12472             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12473             DisplayError(buf, errno);
12474             return FALSE;
12475         } else {
12476             safeStrCpy(buf, lastMsg, MSG_SIZ);
12477             DisplayMessage(_("Waiting for access to save file"), "");
12478             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12479             DisplayMessage(_("Saving game"), "");
12480             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12481             result = SaveGame(f, 0, NULL);
12482             DisplayMessage(buf, "");
12483             return result;
12484         }
12485     }
12486 }
12487
12488 char *
12489 SavePart (char *str)
12490 {
12491     static char buf[MSG_SIZ];
12492     char *p;
12493
12494     p = strchr(str, ' ');
12495     if (p == NULL) return str;
12496     strncpy(buf, str, p - str);
12497     buf[p - str] = NULLCHAR;
12498     return buf;
12499 }
12500
12501 #define PGN_MAX_LINE 75
12502
12503 #define PGN_SIDE_WHITE  0
12504 #define PGN_SIDE_BLACK  1
12505
12506 static int
12507 FindFirstMoveOutOfBook (int side)
12508 {
12509     int result = -1;
12510
12511     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12512         int index = backwardMostMove;
12513         int has_book_hit = 0;
12514
12515         if( (index % 2) != side ) {
12516             index++;
12517         }
12518
12519         while( index < forwardMostMove ) {
12520             /* Check to see if engine is in book */
12521             int depth = pvInfoList[index].depth;
12522             int score = pvInfoList[index].score;
12523             int in_book = 0;
12524
12525             if( depth <= 2 ) {
12526                 in_book = 1;
12527             }
12528             else if( score == 0 && depth == 63 ) {
12529                 in_book = 1; /* Zappa */
12530             }
12531             else if( score == 2 && depth == 99 ) {
12532                 in_book = 1; /* Abrok */
12533             }
12534
12535             has_book_hit += in_book;
12536
12537             if( ! in_book ) {
12538                 result = index;
12539
12540                 break;
12541             }
12542
12543             index += 2;
12544         }
12545     }
12546
12547     return result;
12548 }
12549
12550 void
12551 GetOutOfBookInfo (char * buf)
12552 {
12553     int oob[2];
12554     int i;
12555     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12556
12557     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12558     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12559
12560     *buf = '\0';
12561
12562     if( oob[0] >= 0 || oob[1] >= 0 ) {
12563         for( i=0; i<2; i++ ) {
12564             int idx = oob[i];
12565
12566             if( idx >= 0 ) {
12567                 if( i > 0 && oob[0] >= 0 ) {
12568                     strcat( buf, "   " );
12569                 }
12570
12571                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12572                 sprintf( buf+strlen(buf), "%s%.2f",
12573                     pvInfoList[idx].score >= 0 ? "+" : "",
12574                     pvInfoList[idx].score / 100.0 );
12575             }
12576         }
12577     }
12578 }
12579
12580 /* Save game in PGN style and close the file */
12581 int
12582 SaveGamePGN (FILE *f)
12583 {
12584     int i, offset, linelen, newblock;
12585 //    char *movetext;
12586     char numtext[32];
12587     int movelen, numlen, blank;
12588     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12589
12590     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12591
12592     PrintPGNTags(f, &gameInfo);
12593
12594     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12595
12596     if (backwardMostMove > 0 || startedFromSetupPosition) {
12597         char *fen = PositionToFEN(backwardMostMove, NULL);
12598         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12599         fprintf(f, "\n{--------------\n");
12600         PrintPosition(f, backwardMostMove);
12601         fprintf(f, "--------------}\n");
12602         free(fen);
12603     }
12604     else {
12605         /* [AS] Out of book annotation */
12606         if( appData.saveOutOfBookInfo ) {
12607             char buf[64];
12608
12609             GetOutOfBookInfo( buf );
12610
12611             if( buf[0] != '\0' ) {
12612                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12613             }
12614         }
12615
12616         fprintf(f, "\n");
12617     }
12618
12619     i = backwardMostMove;
12620     linelen = 0;
12621     newblock = TRUE;
12622
12623     while (i < forwardMostMove) {
12624         /* Print comments preceding this move */
12625         if (commentList[i] != NULL) {
12626             if (linelen > 0) fprintf(f, "\n");
12627             fprintf(f, "%s", commentList[i]);
12628             linelen = 0;
12629             newblock = TRUE;
12630         }
12631
12632         /* Format move number */
12633         if ((i % 2) == 0)
12634           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12635         else
12636           if (newblock)
12637             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12638           else
12639             numtext[0] = NULLCHAR;
12640
12641         numlen = strlen(numtext);
12642         newblock = FALSE;
12643
12644         /* Print move number */
12645         blank = linelen > 0 && numlen > 0;
12646         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12647             fprintf(f, "\n");
12648             linelen = 0;
12649             blank = 0;
12650         }
12651         if (blank) {
12652             fprintf(f, " ");
12653             linelen++;
12654         }
12655         fprintf(f, "%s", numtext);
12656         linelen += numlen;
12657
12658         /* Get move */
12659         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12660         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12661
12662         /* Print move */
12663         blank = linelen > 0 && movelen > 0;
12664         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12665             fprintf(f, "\n");
12666             linelen = 0;
12667             blank = 0;
12668         }
12669         if (blank) {
12670             fprintf(f, " ");
12671             linelen++;
12672         }
12673         fprintf(f, "%s", move_buffer);
12674         linelen += movelen;
12675
12676         /* [AS] Add PV info if present */
12677         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12678             /* [HGM] add time */
12679             char buf[MSG_SIZ]; int seconds;
12680
12681             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12682
12683             if( seconds <= 0)
12684               buf[0] = 0;
12685             else
12686               if( seconds < 30 )
12687                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12688               else
12689                 {
12690                   seconds = (seconds + 4)/10; // round to full seconds
12691                   if( seconds < 60 )
12692                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12693                   else
12694                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12695                 }
12696
12697             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12698                       pvInfoList[i].score >= 0 ? "+" : "",
12699                       pvInfoList[i].score / 100.0,
12700                       pvInfoList[i].depth,
12701                       buf );
12702
12703             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12704
12705             /* Print score/depth */
12706             blank = linelen > 0 && movelen > 0;
12707             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12708                 fprintf(f, "\n");
12709                 linelen = 0;
12710                 blank = 0;
12711             }
12712             if (blank) {
12713                 fprintf(f, " ");
12714                 linelen++;
12715             }
12716             fprintf(f, "%s", move_buffer);
12717             linelen += movelen;
12718         }
12719
12720         i++;
12721     }
12722
12723     /* Start a new line */
12724     if (linelen > 0) fprintf(f, "\n");
12725
12726     /* Print comments after last move */
12727     if (commentList[i] != NULL) {
12728         fprintf(f, "%s\n", commentList[i]);
12729     }
12730
12731     /* Print result */
12732     if (gameInfo.resultDetails != NULL &&
12733         gameInfo.resultDetails[0] != NULLCHAR) {
12734         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12735                 PGNResult(gameInfo.result));
12736     } else {
12737         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12738     }
12739
12740     fclose(f);
12741     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12742     return TRUE;
12743 }
12744
12745 /* Save game in old style and close the file */
12746 int
12747 SaveGameOldStyle (FILE *f)
12748 {
12749     int i, offset;
12750     time_t tm;
12751
12752     tm = time((time_t *) NULL);
12753
12754     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12755     PrintOpponents(f);
12756
12757     if (backwardMostMove > 0 || startedFromSetupPosition) {
12758         fprintf(f, "\n[--------------\n");
12759         PrintPosition(f, backwardMostMove);
12760         fprintf(f, "--------------]\n");
12761     } else {
12762         fprintf(f, "\n");
12763     }
12764
12765     i = backwardMostMove;
12766     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12767
12768     while (i < forwardMostMove) {
12769         if (commentList[i] != NULL) {
12770             fprintf(f, "[%s]\n", commentList[i]);
12771         }
12772
12773         if ((i % 2) == 1) {
12774             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12775             i++;
12776         } else {
12777             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12778             i++;
12779             if (commentList[i] != NULL) {
12780                 fprintf(f, "\n");
12781                 continue;
12782             }
12783             if (i >= forwardMostMove) {
12784                 fprintf(f, "\n");
12785                 break;
12786             }
12787             fprintf(f, "%s\n", parseList[i]);
12788             i++;
12789         }
12790     }
12791
12792     if (commentList[i] != NULL) {
12793         fprintf(f, "[%s]\n", commentList[i]);
12794     }
12795
12796     /* This isn't really the old style, but it's close enough */
12797     if (gameInfo.resultDetails != NULL &&
12798         gameInfo.resultDetails[0] != NULLCHAR) {
12799         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12800                 gameInfo.resultDetails);
12801     } else {
12802         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12803     }
12804
12805     fclose(f);
12806     return TRUE;
12807 }
12808
12809 /* Save the current game to open file f and close the file */
12810 int
12811 SaveGame (FILE *f, int dummy, char *dummy2)
12812 {
12813     if (gameMode == EditPosition) EditPositionDone(TRUE);
12814     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12815     if (appData.oldSaveStyle)
12816       return SaveGameOldStyle(f);
12817     else
12818       return SaveGamePGN(f);
12819 }
12820
12821 /* Save the current position to the given file */
12822 int
12823 SavePositionToFile (char *filename)
12824 {
12825     FILE *f;
12826     char buf[MSG_SIZ];
12827
12828     if (strcmp(filename, "-") == 0) {
12829         return SavePosition(stdout, 0, NULL);
12830     } else {
12831         f = fopen(filename, "a");
12832         if (f == NULL) {
12833             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12834             DisplayError(buf, errno);
12835             return FALSE;
12836         } else {
12837             safeStrCpy(buf, lastMsg, MSG_SIZ);
12838             DisplayMessage(_("Waiting for access to save file"), "");
12839             flock(fileno(f), LOCK_EX); // [HGM] lock
12840             DisplayMessage(_("Saving position"), "");
12841             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12842             SavePosition(f, 0, NULL);
12843             DisplayMessage(buf, "");
12844             return TRUE;
12845         }
12846     }
12847 }
12848
12849 /* Save the current position to the given open file and close the file */
12850 int
12851 SavePosition (FILE *f, int dummy, char *dummy2)
12852 {
12853     time_t tm;
12854     char *fen;
12855
12856     if (gameMode == EditPosition) EditPositionDone(TRUE);
12857     if (appData.oldSaveStyle) {
12858         tm = time((time_t *) NULL);
12859
12860         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12861         PrintOpponents(f);
12862         fprintf(f, "[--------------\n");
12863         PrintPosition(f, currentMove);
12864         fprintf(f, "--------------]\n");
12865     } else {
12866         fen = PositionToFEN(currentMove, NULL);
12867         fprintf(f, "%s\n", fen);
12868         free(fen);
12869     }
12870     fclose(f);
12871     return TRUE;
12872 }
12873
12874 void
12875 ReloadCmailMsgEvent (int unregister)
12876 {
12877 #if !WIN32
12878     static char *inFilename = NULL;
12879     static char *outFilename;
12880     int i;
12881     struct stat inbuf, outbuf;
12882     int status;
12883
12884     /* Any registered moves are unregistered if unregister is set, */
12885     /* i.e. invoked by the signal handler */
12886     if (unregister) {
12887         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12888             cmailMoveRegistered[i] = FALSE;
12889             if (cmailCommentList[i] != NULL) {
12890                 free(cmailCommentList[i]);
12891                 cmailCommentList[i] = NULL;
12892             }
12893         }
12894         nCmailMovesRegistered = 0;
12895     }
12896
12897     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12898         cmailResult[i] = CMAIL_NOT_RESULT;
12899     }
12900     nCmailResults = 0;
12901
12902     if (inFilename == NULL) {
12903         /* Because the filenames are static they only get malloced once  */
12904         /* and they never get freed                                      */
12905         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12906         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12907
12908         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12909         sprintf(outFilename, "%s.out", appData.cmailGameName);
12910     }
12911
12912     status = stat(outFilename, &outbuf);
12913     if (status < 0) {
12914         cmailMailedMove = FALSE;
12915     } else {
12916         status = stat(inFilename, &inbuf);
12917         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12918     }
12919
12920     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12921        counts the games, notes how each one terminated, etc.
12922
12923        It would be nice to remove this kludge and instead gather all
12924        the information while building the game list.  (And to keep it
12925        in the game list nodes instead of having a bunch of fixed-size
12926        parallel arrays.)  Note this will require getting each game's
12927        termination from the PGN tags, as the game list builder does
12928        not process the game moves.  --mann
12929        */
12930     cmailMsgLoaded = TRUE;
12931     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12932
12933     /* Load first game in the file or popup game menu */
12934     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12935
12936 #endif /* !WIN32 */
12937     return;
12938 }
12939
12940 int
12941 RegisterMove ()
12942 {
12943     FILE *f;
12944     char string[MSG_SIZ];
12945
12946     if (   cmailMailedMove
12947         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12948         return TRUE;            /* Allow free viewing  */
12949     }
12950
12951     /* Unregister move to ensure that we don't leave RegisterMove        */
12952     /* with the move registered when the conditions for registering no   */
12953     /* longer hold                                                       */
12954     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12955         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12956         nCmailMovesRegistered --;
12957
12958         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12959           {
12960               free(cmailCommentList[lastLoadGameNumber - 1]);
12961               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12962           }
12963     }
12964
12965     if (cmailOldMove == -1) {
12966         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12967         return FALSE;
12968     }
12969
12970     if (currentMove > cmailOldMove + 1) {
12971         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12972         return FALSE;
12973     }
12974
12975     if (currentMove < cmailOldMove) {
12976         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12977         return FALSE;
12978     }
12979
12980     if (forwardMostMove > currentMove) {
12981         /* Silently truncate extra moves */
12982         TruncateGame();
12983     }
12984
12985     if (   (currentMove == cmailOldMove + 1)
12986         || (   (currentMove == cmailOldMove)
12987             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12988                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12989         if (gameInfo.result != GameUnfinished) {
12990             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12991         }
12992
12993         if (commentList[currentMove] != NULL) {
12994             cmailCommentList[lastLoadGameNumber - 1]
12995               = StrSave(commentList[currentMove]);
12996         }
12997         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12998
12999         if (appData.debugMode)
13000           fprintf(debugFP, "Saving %s for game %d\n",
13001                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13002
13003         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13004
13005         f = fopen(string, "w");
13006         if (appData.oldSaveStyle) {
13007             SaveGameOldStyle(f); /* also closes the file */
13008
13009             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13010             f = fopen(string, "w");
13011             SavePosition(f, 0, NULL); /* also closes the file */
13012         } else {
13013             fprintf(f, "{--------------\n");
13014             PrintPosition(f, currentMove);
13015             fprintf(f, "--------------}\n\n");
13016
13017             SaveGame(f, 0, NULL); /* also closes the file*/
13018         }
13019
13020         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13021         nCmailMovesRegistered ++;
13022     } else if (nCmailGames == 1) {
13023         DisplayError(_("You have not made a move yet"), 0);
13024         return FALSE;
13025     }
13026
13027     return TRUE;
13028 }
13029
13030 void
13031 MailMoveEvent ()
13032 {
13033 #if !WIN32
13034     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13035     FILE *commandOutput;
13036     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13037     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13038     int nBuffers;
13039     int i;
13040     int archived;
13041     char *arcDir;
13042
13043     if (! cmailMsgLoaded) {
13044         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13045         return;
13046     }
13047
13048     if (nCmailGames == nCmailResults) {
13049         DisplayError(_("No unfinished games"), 0);
13050         return;
13051     }
13052
13053 #if CMAIL_PROHIBIT_REMAIL
13054     if (cmailMailedMove) {
13055       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);
13056         DisplayError(msg, 0);
13057         return;
13058     }
13059 #endif
13060
13061     if (! (cmailMailedMove || RegisterMove())) return;
13062
13063     if (   cmailMailedMove
13064         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13065       snprintf(string, MSG_SIZ, partCommandString,
13066                appData.debugMode ? " -v" : "", appData.cmailGameName);
13067         commandOutput = popen(string, "r");
13068
13069         if (commandOutput == NULL) {
13070             DisplayError(_("Failed to invoke cmail"), 0);
13071         } else {
13072             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13073                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13074             }
13075             if (nBuffers > 1) {
13076                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13077                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13078                 nBytes = MSG_SIZ - 1;
13079             } else {
13080                 (void) memcpy(msg, buffer, nBytes);
13081             }
13082             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13083
13084             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13085                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13086
13087                 archived = TRUE;
13088                 for (i = 0; i < nCmailGames; i ++) {
13089                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13090                         archived = FALSE;
13091                     }
13092                 }
13093                 if (   archived
13094                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13095                         != NULL)) {
13096                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13097                            arcDir,
13098                            appData.cmailGameName,
13099                            gameInfo.date);
13100                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13101                     cmailMsgLoaded = FALSE;
13102                 }
13103             }
13104
13105             DisplayInformation(msg);
13106             pclose(commandOutput);
13107         }
13108     } else {
13109         if ((*cmailMsg) != '\0') {
13110             DisplayInformation(cmailMsg);
13111         }
13112     }
13113
13114     return;
13115 #endif /* !WIN32 */
13116 }
13117
13118 char *
13119 CmailMsg ()
13120 {
13121 #if WIN32
13122     return NULL;
13123 #else
13124     int  prependComma = 0;
13125     char number[5];
13126     char string[MSG_SIZ];       /* Space for game-list */
13127     int  i;
13128
13129     if (!cmailMsgLoaded) return "";
13130
13131     if (cmailMailedMove) {
13132       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13133     } else {
13134         /* Create a list of games left */
13135       snprintf(string, MSG_SIZ, "[");
13136         for (i = 0; i < nCmailGames; i ++) {
13137             if (! (   cmailMoveRegistered[i]
13138                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13139                 if (prependComma) {
13140                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13141                 } else {
13142                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13143                     prependComma = 1;
13144                 }
13145
13146                 strcat(string, number);
13147             }
13148         }
13149         strcat(string, "]");
13150
13151         if (nCmailMovesRegistered + nCmailResults == 0) {
13152             switch (nCmailGames) {
13153               case 1:
13154                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13155                 break;
13156
13157               case 2:
13158                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13159                 break;
13160
13161               default:
13162                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13163                          nCmailGames);
13164                 break;
13165             }
13166         } else {
13167             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13168               case 1:
13169                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13170                          string);
13171                 break;
13172
13173               case 0:
13174                 if (nCmailResults == nCmailGames) {
13175                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13176                 } else {
13177                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13178                 }
13179                 break;
13180
13181               default:
13182                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13183                          string);
13184             }
13185         }
13186     }
13187     return cmailMsg;
13188 #endif /* WIN32 */
13189 }
13190
13191 void
13192 ResetGameEvent ()
13193 {
13194     if (gameMode == Training)
13195       SetTrainingModeOff();
13196
13197     Reset(TRUE, TRUE);
13198     cmailMsgLoaded = FALSE;
13199     if (appData.icsActive) {
13200       SendToICS(ics_prefix);
13201       SendToICS("refresh\n");
13202     }
13203 }
13204
13205 void
13206 ExitEvent (int status)
13207 {
13208     exiting++;
13209     if (exiting > 2) {
13210       /* Give up on clean exit */
13211       exit(status);
13212     }
13213     if (exiting > 1) {
13214       /* Keep trying for clean exit */
13215       return;
13216     }
13217
13218     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13219
13220     if (telnetISR != NULL) {
13221       RemoveInputSource(telnetISR);
13222     }
13223     if (icsPR != NoProc) {
13224       DestroyChildProcess(icsPR, TRUE);
13225     }
13226
13227     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13228     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13229
13230     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13231     /* make sure this other one finishes before killing it!                  */
13232     if(endingGame) { int count = 0;
13233         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13234         while(endingGame && count++ < 10) DoSleep(1);
13235         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13236     }
13237
13238     /* Kill off chess programs */
13239     if (first.pr != NoProc) {
13240         ExitAnalyzeMode();
13241
13242         DoSleep( appData.delayBeforeQuit );
13243         SendToProgram("quit\n", &first);
13244         DoSleep( appData.delayAfterQuit );
13245         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13246     }
13247     if (second.pr != NoProc) {
13248         DoSleep( appData.delayBeforeQuit );
13249         SendToProgram("quit\n", &second);
13250         DoSleep( appData.delayAfterQuit );
13251         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13252     }
13253     if (first.isr != NULL) {
13254         RemoveInputSource(first.isr);
13255     }
13256     if (second.isr != NULL) {
13257         RemoveInputSource(second.isr);
13258     }
13259
13260     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13261     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13262
13263     ShutDownFrontEnd();
13264     exit(status);
13265 }
13266
13267 void
13268 PauseEvent ()
13269 {
13270     if (appData.debugMode)
13271         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13272     if (pausing) {
13273         pausing = FALSE;
13274         ModeHighlight();
13275         if (gameMode == MachinePlaysWhite ||
13276             gameMode == MachinePlaysBlack) {
13277             StartClocks();
13278         } else {
13279             DisplayBothClocks();
13280         }
13281         if (gameMode == PlayFromGameFile) {
13282             if (appData.timeDelay >= 0)
13283                 AutoPlayGameLoop();
13284         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13285             Reset(FALSE, TRUE);
13286             SendToICS(ics_prefix);
13287             SendToICS("refresh\n");
13288         } else if (currentMove < forwardMostMove) {
13289             ForwardInner(forwardMostMove);
13290         }
13291         pauseExamInvalid = FALSE;
13292     } else {
13293         switch (gameMode) {
13294           default:
13295             return;
13296           case IcsExamining:
13297             pauseExamForwardMostMove = forwardMostMove;
13298             pauseExamInvalid = FALSE;
13299             /* fall through */
13300           case IcsObserving:
13301           case IcsPlayingWhite:
13302           case IcsPlayingBlack:
13303             pausing = TRUE;
13304             ModeHighlight();
13305             return;
13306           case PlayFromGameFile:
13307             (void) StopLoadGameTimer();
13308             pausing = TRUE;
13309             ModeHighlight();
13310             break;
13311           case BeginningOfGame:
13312             if (appData.icsActive) return;
13313             /* else fall through */
13314           case MachinePlaysWhite:
13315           case MachinePlaysBlack:
13316           case TwoMachinesPlay:
13317             if (forwardMostMove == 0)
13318               return;           /* don't pause if no one has moved */
13319             if ((gameMode == MachinePlaysWhite &&
13320                  !WhiteOnMove(forwardMostMove)) ||
13321                 (gameMode == MachinePlaysBlack &&
13322                  WhiteOnMove(forwardMostMove))) {
13323                 StopClocks();
13324             }
13325             pausing = TRUE;
13326             ModeHighlight();
13327             break;
13328         }
13329     }
13330 }
13331
13332 void
13333 EditCommentEvent ()
13334 {
13335     char title[MSG_SIZ];
13336
13337     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13338       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13339     } else {
13340       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13341                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13342                parseList[currentMove - 1]);
13343     }
13344
13345     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13346 }
13347
13348
13349 void
13350 EditTagsEvent ()
13351 {
13352     char *tags = PGNTags(&gameInfo);
13353     bookUp = FALSE;
13354     EditTagsPopUp(tags, NULL);
13355     free(tags);
13356 }
13357
13358 void
13359 AnalyzeModeEvent ()
13360 {
13361     if (appData.noChessProgram || gameMode == AnalyzeMode)
13362       return;
13363
13364     if (gameMode != AnalyzeFile) {
13365         if (!appData.icsEngineAnalyze) {
13366                EditGameEvent();
13367                if (gameMode != EditGame) return;
13368         }
13369         ResurrectChessProgram();
13370         SendToProgram("analyze\n", &first);
13371         first.analyzing = TRUE;
13372         /*first.maybeThinking = TRUE;*/
13373         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13374         EngineOutputPopUp();
13375     }
13376     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13377     pausing = FALSE;
13378     ModeHighlight();
13379     SetGameInfo();
13380
13381     StartAnalysisClock();
13382     GetTimeMark(&lastNodeCountTime);
13383     lastNodeCount = 0;
13384 }
13385
13386 void
13387 AnalyzeFileEvent ()
13388 {
13389     if (appData.noChessProgram || gameMode == AnalyzeFile)
13390       return;
13391
13392     if (gameMode != AnalyzeMode) {
13393         EditGameEvent();
13394         if (gameMode != EditGame) return;
13395         ResurrectChessProgram();
13396         SendToProgram("analyze\n", &first);
13397         first.analyzing = TRUE;
13398         /*first.maybeThinking = TRUE;*/
13399         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13400         EngineOutputPopUp();
13401     }
13402     gameMode = AnalyzeFile;
13403     pausing = FALSE;
13404     ModeHighlight();
13405     SetGameInfo();
13406
13407     StartAnalysisClock();
13408     GetTimeMark(&lastNodeCountTime);
13409     lastNodeCount = 0;
13410     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13411 }
13412
13413 void
13414 MachineWhiteEvent ()
13415 {
13416     char buf[MSG_SIZ];
13417     char *bookHit = NULL;
13418
13419     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13420       return;
13421
13422
13423     if (gameMode == PlayFromGameFile ||
13424         gameMode == TwoMachinesPlay  ||
13425         gameMode == Training         ||
13426         gameMode == AnalyzeMode      ||
13427         gameMode == EndOfGame)
13428         EditGameEvent();
13429
13430     if (gameMode == EditPosition)
13431         EditPositionDone(TRUE);
13432
13433     if (!WhiteOnMove(currentMove)) {
13434         DisplayError(_("It is not White's turn"), 0);
13435         return;
13436     }
13437
13438     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13439       ExitAnalyzeMode();
13440
13441     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13442         gameMode == AnalyzeFile)
13443         TruncateGame();
13444
13445     ResurrectChessProgram();    /* in case it isn't running */
13446     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13447         gameMode = MachinePlaysWhite;
13448         ResetClocks();
13449     } else
13450     gameMode = MachinePlaysWhite;
13451     pausing = FALSE;
13452     ModeHighlight();
13453     SetGameInfo();
13454     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13455     DisplayTitle(buf);
13456     if (first.sendName) {
13457       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13458       SendToProgram(buf, &first);
13459     }
13460     if (first.sendTime) {
13461       if (first.useColors) {
13462         SendToProgram("black\n", &first); /*gnu kludge*/
13463       }
13464       SendTimeRemaining(&first, TRUE);
13465     }
13466     if (first.useColors) {
13467       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13468     }
13469     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13470     SetMachineThinkingEnables();
13471     first.maybeThinking = TRUE;
13472     StartClocks();
13473     firstMove = FALSE;
13474
13475     if (appData.autoFlipView && !flipView) {
13476       flipView = !flipView;
13477       DrawPosition(FALSE, NULL);
13478       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13479     }
13480
13481     if(bookHit) { // [HGM] book: simulate book reply
13482         static char bookMove[MSG_SIZ]; // a bit generous?
13483
13484         programStats.nodes = programStats.depth = programStats.time =
13485         programStats.score = programStats.got_only_move = 0;
13486         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13487
13488         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13489         strcat(bookMove, bookHit);
13490         HandleMachineMove(bookMove, &first);
13491     }
13492 }
13493
13494 void
13495 MachineBlackEvent ()
13496 {
13497   char buf[MSG_SIZ];
13498   char *bookHit = NULL;
13499
13500     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13501         return;
13502
13503
13504     if (gameMode == PlayFromGameFile ||
13505         gameMode == TwoMachinesPlay  ||
13506         gameMode == Training         ||
13507         gameMode == AnalyzeMode      ||
13508         gameMode == EndOfGame)
13509         EditGameEvent();
13510
13511     if (gameMode == EditPosition)
13512         EditPositionDone(TRUE);
13513
13514     if (WhiteOnMove(currentMove)) {
13515         DisplayError(_("It is not Black's turn"), 0);
13516         return;
13517     }
13518
13519     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13520       ExitAnalyzeMode();
13521
13522     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13523         gameMode == AnalyzeFile)
13524         TruncateGame();
13525
13526     ResurrectChessProgram();    /* in case it isn't running */
13527     gameMode = MachinePlaysBlack;
13528     pausing = FALSE;
13529     ModeHighlight();
13530     SetGameInfo();
13531     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13532     DisplayTitle(buf);
13533     if (first.sendName) {
13534       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13535       SendToProgram(buf, &first);
13536     }
13537     if (first.sendTime) {
13538       if (first.useColors) {
13539         SendToProgram("white\n", &first); /*gnu kludge*/
13540       }
13541       SendTimeRemaining(&first, FALSE);
13542     }
13543     if (first.useColors) {
13544       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13545     }
13546     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13547     SetMachineThinkingEnables();
13548     first.maybeThinking = TRUE;
13549     StartClocks();
13550
13551     if (appData.autoFlipView && flipView) {
13552       flipView = !flipView;
13553       DrawPosition(FALSE, NULL);
13554       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13555     }
13556     if(bookHit) { // [HGM] book: simulate book reply
13557         static char bookMove[MSG_SIZ]; // a bit generous?
13558
13559         programStats.nodes = programStats.depth = programStats.time =
13560         programStats.score = programStats.got_only_move = 0;
13561         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13562
13563         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13564         strcat(bookMove, bookHit);
13565         HandleMachineMove(bookMove, &first);
13566     }
13567 }
13568
13569
13570 void
13571 DisplayTwoMachinesTitle ()
13572 {
13573     char buf[MSG_SIZ];
13574     if (appData.matchGames > 0) {
13575         if(appData.tourneyFile[0]) {
13576           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13577                    gameInfo.white, _("vs."), gameInfo.black,
13578                    nextGame+1, appData.matchGames+1,
13579                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13580         } else 
13581         if (first.twoMachinesColor[0] == 'w') {
13582           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13583                    gameInfo.white, _("vs."),  gameInfo.black,
13584                    first.matchWins, second.matchWins,
13585                    matchGame - 1 - (first.matchWins + second.matchWins));
13586         } else {
13587           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13588                    gameInfo.white, _("vs."), gameInfo.black,
13589                    second.matchWins, first.matchWins,
13590                    matchGame - 1 - (first.matchWins + second.matchWins));
13591         }
13592     } else {
13593       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13594     }
13595     DisplayTitle(buf);
13596 }
13597
13598 void
13599 SettingsMenuIfReady ()
13600 {
13601   if (second.lastPing != second.lastPong) {
13602     DisplayMessage("", _("Waiting for second chess program"));
13603     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13604     return;
13605   }
13606   ThawUI();
13607   DisplayMessage("", "");
13608   SettingsPopUp(&second);
13609 }
13610
13611 int
13612 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13613 {
13614     char buf[MSG_SIZ];
13615     if (cps->pr == NoProc) {
13616         StartChessProgram(cps);
13617         if (cps->protocolVersion == 1) {
13618           retry();
13619         } else {
13620           /* kludge: allow timeout for initial "feature" command */
13621           FreezeUI();
13622           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13623           DisplayMessage("", buf);
13624           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13625         }
13626         return 1;
13627     }
13628     return 0;
13629 }
13630
13631 void
13632 TwoMachinesEvent P((void))
13633 {
13634     int i;
13635     char buf[MSG_SIZ];
13636     ChessProgramState *onmove;
13637     char *bookHit = NULL;
13638     static int stalling = 0;
13639     TimeMark now;
13640     long wait;
13641
13642     if (appData.noChessProgram) return;
13643
13644     switch (gameMode) {
13645       case TwoMachinesPlay:
13646         return;
13647       case MachinePlaysWhite:
13648       case MachinePlaysBlack:
13649         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13650             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13651             return;
13652         }
13653         /* fall through */
13654       case BeginningOfGame:
13655       case PlayFromGameFile:
13656       case EndOfGame:
13657         EditGameEvent();
13658         if (gameMode != EditGame) return;
13659         break;
13660       case EditPosition:
13661         EditPositionDone(TRUE);
13662         break;
13663       case AnalyzeMode:
13664       case AnalyzeFile:
13665         ExitAnalyzeMode();
13666         break;
13667       case EditGame:
13668       default:
13669         break;
13670     }
13671
13672 //    forwardMostMove = currentMove;
13673     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13674
13675     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13676
13677     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13678     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13679       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13680       return;
13681     }
13682
13683     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13684         DisplayError("second engine does not play this", 0);
13685         return;
13686     }
13687
13688     if(!stalling) {
13689       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13690       SendToProgram("force\n", &second);
13691       stalling = 1;
13692       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13693       return;
13694     }
13695     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13696     if(appData.matchPause>10000 || appData.matchPause<10)
13697                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13698     wait = SubtractTimeMarks(&now, &pauseStart);
13699     if(wait < appData.matchPause) {
13700         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13701         return;
13702     }
13703     // we are now committed to starting the game
13704     stalling = 0;
13705     DisplayMessage("", "");
13706     if (startedFromSetupPosition) {
13707         SendBoard(&second, backwardMostMove);
13708     if (appData.debugMode) {
13709         fprintf(debugFP, "Two Machines\n");
13710     }
13711     }
13712     for (i = backwardMostMove; i < forwardMostMove; i++) {
13713         SendMoveToProgram(i, &second);
13714     }
13715
13716     gameMode = TwoMachinesPlay;
13717     pausing = FALSE;
13718     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13719     SetGameInfo();
13720     DisplayTwoMachinesTitle();
13721     firstMove = TRUE;
13722     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13723         onmove = &first;
13724     } else {
13725         onmove = &second;
13726     }
13727     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13728     SendToProgram(first.computerString, &first);
13729     if (first.sendName) {
13730       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13731       SendToProgram(buf, &first);
13732     }
13733     SendToProgram(second.computerString, &second);
13734     if (second.sendName) {
13735       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13736       SendToProgram(buf, &second);
13737     }
13738
13739     ResetClocks();
13740     if (!first.sendTime || !second.sendTime) {
13741         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13742         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13743     }
13744     if (onmove->sendTime) {
13745       if (onmove->useColors) {
13746         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13747       }
13748       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13749     }
13750     if (onmove->useColors) {
13751       SendToProgram(onmove->twoMachinesColor, onmove);
13752     }
13753     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13754 //    SendToProgram("go\n", onmove);
13755     onmove->maybeThinking = TRUE;
13756     SetMachineThinkingEnables();
13757
13758     StartClocks();
13759
13760     if(bookHit) { // [HGM] book: simulate book reply
13761         static char bookMove[MSG_SIZ]; // a bit generous?
13762
13763         programStats.nodes = programStats.depth = programStats.time =
13764         programStats.score = programStats.got_only_move = 0;
13765         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13766
13767         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13768         strcat(bookMove, bookHit);
13769         savedMessage = bookMove; // args for deferred call
13770         savedState = onmove;
13771         ScheduleDelayedEvent(DeferredBookMove, 1);
13772     }
13773 }
13774
13775 void
13776 TrainingEvent ()
13777 {
13778     if (gameMode == Training) {
13779       SetTrainingModeOff();
13780       gameMode = PlayFromGameFile;
13781       DisplayMessage("", _("Training mode off"));
13782     } else {
13783       gameMode = Training;
13784       animateTraining = appData.animate;
13785
13786       /* make sure we are not already at the end of the game */
13787       if (currentMove < forwardMostMove) {
13788         SetTrainingModeOn();
13789         DisplayMessage("", _("Training mode on"));
13790       } else {
13791         gameMode = PlayFromGameFile;
13792         DisplayError(_("Already at end of game"), 0);
13793       }
13794     }
13795     ModeHighlight();
13796 }
13797
13798 void
13799 IcsClientEvent ()
13800 {
13801     if (!appData.icsActive) return;
13802     switch (gameMode) {
13803       case IcsPlayingWhite:
13804       case IcsPlayingBlack:
13805       case IcsObserving:
13806       case IcsIdle:
13807       case BeginningOfGame:
13808       case IcsExamining:
13809         return;
13810
13811       case EditGame:
13812         break;
13813
13814       case EditPosition:
13815         EditPositionDone(TRUE);
13816         break;
13817
13818       case AnalyzeMode:
13819       case AnalyzeFile:
13820         ExitAnalyzeMode();
13821         break;
13822
13823       default:
13824         EditGameEvent();
13825         break;
13826     }
13827
13828     gameMode = IcsIdle;
13829     ModeHighlight();
13830     return;
13831 }
13832
13833 void
13834 EditGameEvent ()
13835 {
13836     int i;
13837
13838     switch (gameMode) {
13839       case Training:
13840         SetTrainingModeOff();
13841         break;
13842       case MachinePlaysWhite:
13843       case MachinePlaysBlack:
13844       case BeginningOfGame:
13845         SendToProgram("force\n", &first);
13846         SetUserThinkingEnables();
13847         break;
13848       case PlayFromGameFile:
13849         (void) StopLoadGameTimer();
13850         if (gameFileFP != NULL) {
13851             gameFileFP = NULL;
13852         }
13853         break;
13854       case EditPosition:
13855         EditPositionDone(TRUE);
13856         break;
13857       case AnalyzeMode:
13858       case AnalyzeFile:
13859         ExitAnalyzeMode();
13860         SendToProgram("force\n", &first);
13861         break;
13862       case TwoMachinesPlay:
13863         GameEnds(EndOfFile, NULL, GE_PLAYER);
13864         ResurrectChessProgram();
13865         SetUserThinkingEnables();
13866         break;
13867       case EndOfGame:
13868         ResurrectChessProgram();
13869         break;
13870       case IcsPlayingBlack:
13871       case IcsPlayingWhite:
13872         DisplayError(_("Warning: You are still playing a game"), 0);
13873         break;
13874       case IcsObserving:
13875         DisplayError(_("Warning: You are still observing a game"), 0);
13876         break;
13877       case IcsExamining:
13878         DisplayError(_("Warning: You are still examining a game"), 0);
13879         break;
13880       case IcsIdle:
13881         break;
13882       case EditGame:
13883       default:
13884         return;
13885     }
13886
13887     pausing = FALSE;
13888     StopClocks();
13889     first.offeredDraw = second.offeredDraw = 0;
13890
13891     if (gameMode == PlayFromGameFile) {
13892         whiteTimeRemaining = timeRemaining[0][currentMove];
13893         blackTimeRemaining = timeRemaining[1][currentMove];
13894         DisplayTitle("");
13895     }
13896
13897     if (gameMode == MachinePlaysWhite ||
13898         gameMode == MachinePlaysBlack ||
13899         gameMode == TwoMachinesPlay ||
13900         gameMode == EndOfGame) {
13901         i = forwardMostMove;
13902         while (i > currentMove) {
13903             SendToProgram("undo\n", &first);
13904             i--;
13905         }
13906         if(!adjustedClock) {
13907         whiteTimeRemaining = timeRemaining[0][currentMove];
13908         blackTimeRemaining = timeRemaining[1][currentMove];
13909         DisplayBothClocks();
13910         }
13911         if (whiteFlag || blackFlag) {
13912             whiteFlag = blackFlag = 0;
13913         }
13914         DisplayTitle("");
13915     }
13916
13917     gameMode = EditGame;
13918     ModeHighlight();
13919     SetGameInfo();
13920 }
13921
13922
13923 void
13924 EditPositionEvent ()
13925 {
13926     if (gameMode == EditPosition) {
13927         EditGameEvent();
13928         return;
13929     }
13930
13931     EditGameEvent();
13932     if (gameMode != EditGame) return;
13933
13934     gameMode = EditPosition;
13935     ModeHighlight();
13936     SetGameInfo();
13937     if (currentMove > 0)
13938       CopyBoard(boards[0], boards[currentMove]);
13939
13940     blackPlaysFirst = !WhiteOnMove(currentMove);
13941     ResetClocks();
13942     currentMove = forwardMostMove = backwardMostMove = 0;
13943     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13944     DisplayMove(-1);
13945     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13946 }
13947
13948 void
13949 ExitAnalyzeMode ()
13950 {
13951     /* [DM] icsEngineAnalyze - possible call from other functions */
13952     if (appData.icsEngineAnalyze) {
13953         appData.icsEngineAnalyze = FALSE;
13954
13955         DisplayMessage("",_("Close ICS engine analyze..."));
13956     }
13957     if (first.analysisSupport && first.analyzing) {
13958       SendToProgram("exit\n", &first);
13959       first.analyzing = FALSE;
13960     }
13961     thinkOutput[0] = NULLCHAR;
13962 }
13963
13964 void
13965 EditPositionDone (Boolean fakeRights)
13966 {
13967     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13968
13969     startedFromSetupPosition = TRUE;
13970     InitChessProgram(&first, FALSE);
13971     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13972       boards[0][EP_STATUS] = EP_NONE;
13973       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13974       if(boards[0][0][BOARD_WIDTH>>1] == king) {
13975         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
13976         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13977       } else boards[0][CASTLING][2] = NoRights;
13978       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13979         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
13980         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13981       } else boards[0][CASTLING][5] = NoRights;
13982       if(gameInfo.variant == VariantSChess) {
13983         int i;
13984         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
13985           boards[0][VIRGIN][i] = 0;
13986           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
13987           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
13988         }
13989       }
13990     }
13991     SendToProgram("force\n", &first);
13992     if (blackPlaysFirst) {
13993         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13994         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13995         currentMove = forwardMostMove = backwardMostMove = 1;
13996         CopyBoard(boards[1], boards[0]);
13997     } else {
13998         currentMove = forwardMostMove = backwardMostMove = 0;
13999     }
14000     SendBoard(&first, forwardMostMove);
14001     if (appData.debugMode) {
14002         fprintf(debugFP, "EditPosDone\n");
14003     }
14004     DisplayTitle("");
14005     DisplayMessage("", "");
14006     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14007     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14008     gameMode = EditGame;
14009     ModeHighlight();
14010     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14011     ClearHighlights(); /* [AS] */
14012 }
14013
14014 /* Pause for `ms' milliseconds */
14015 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14016 void
14017 TimeDelay (long ms)
14018 {
14019     TimeMark m1, m2;
14020
14021     GetTimeMark(&m1);
14022     do {
14023         GetTimeMark(&m2);
14024     } while (SubtractTimeMarks(&m2, &m1) < ms);
14025 }
14026
14027 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14028 void
14029 SendMultiLineToICS (char *buf)
14030 {
14031     char temp[MSG_SIZ+1], *p;
14032     int len;
14033
14034     len = strlen(buf);
14035     if (len > MSG_SIZ)
14036       len = MSG_SIZ;
14037
14038     strncpy(temp, buf, len);
14039     temp[len] = 0;
14040
14041     p = temp;
14042     while (*p) {
14043         if (*p == '\n' || *p == '\r')
14044           *p = ' ';
14045         ++p;
14046     }
14047
14048     strcat(temp, "\n");
14049     SendToICS(temp);
14050     SendToPlayer(temp, strlen(temp));
14051 }
14052
14053 void
14054 SetWhiteToPlayEvent ()
14055 {
14056     if (gameMode == EditPosition) {
14057         blackPlaysFirst = FALSE;
14058         DisplayBothClocks();    /* works because currentMove is 0 */
14059     } else if (gameMode == IcsExamining) {
14060         SendToICS(ics_prefix);
14061         SendToICS("tomove white\n");
14062     }
14063 }
14064
14065 void
14066 SetBlackToPlayEvent ()
14067 {
14068     if (gameMode == EditPosition) {
14069         blackPlaysFirst = TRUE;
14070         currentMove = 1;        /* kludge */
14071         DisplayBothClocks();
14072         currentMove = 0;
14073     } else if (gameMode == IcsExamining) {
14074         SendToICS(ics_prefix);
14075         SendToICS("tomove black\n");
14076     }
14077 }
14078
14079 void
14080 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14081 {
14082     char buf[MSG_SIZ];
14083     ChessSquare piece = boards[0][y][x];
14084
14085     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14086
14087     switch (selection) {
14088       case ClearBoard:
14089         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14090             SendToICS(ics_prefix);
14091             SendToICS("bsetup clear\n");
14092         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14093             SendToICS(ics_prefix);
14094             SendToICS("clearboard\n");
14095         } else {
14096             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14097                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14098                 for (y = 0; y < BOARD_HEIGHT; y++) {
14099                     if (gameMode == IcsExamining) {
14100                         if (boards[currentMove][y][x] != EmptySquare) {
14101                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14102                                     AAA + x, ONE + y);
14103                             SendToICS(buf);
14104                         }
14105                     } else {
14106                         boards[0][y][x] = p;
14107                     }
14108                 }
14109             }
14110         }
14111         if (gameMode == EditPosition) {
14112             DrawPosition(FALSE, boards[0]);
14113         }
14114         break;
14115
14116       case WhitePlay:
14117         SetWhiteToPlayEvent();
14118         break;
14119
14120       case BlackPlay:
14121         SetBlackToPlayEvent();
14122         break;
14123
14124       case EmptySquare:
14125         if (gameMode == IcsExamining) {
14126             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14127             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14128             SendToICS(buf);
14129         } else {
14130             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14131                 if(x == BOARD_LEFT-2) {
14132                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14133                     boards[0][y][1] = 0;
14134                 } else
14135                 if(x == BOARD_RGHT+1) {
14136                     if(y >= gameInfo.holdingsSize) break;
14137                     boards[0][y][BOARD_WIDTH-2] = 0;
14138                 } else break;
14139             }
14140             boards[0][y][x] = EmptySquare;
14141             DrawPosition(FALSE, boards[0]);
14142         }
14143         break;
14144
14145       case PromotePiece:
14146         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14147            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14148             selection = (ChessSquare) (PROMOTED piece);
14149         } else if(piece == EmptySquare) selection = WhiteSilver;
14150         else selection = (ChessSquare)((int)piece - 1);
14151         goto defaultlabel;
14152
14153       case DemotePiece:
14154         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14155            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14156             selection = (ChessSquare) (DEMOTED piece);
14157         } else if(piece == EmptySquare) selection = BlackSilver;
14158         else selection = (ChessSquare)((int)piece + 1);
14159         goto defaultlabel;
14160
14161       case WhiteQueen:
14162       case BlackQueen:
14163         if(gameInfo.variant == VariantShatranj ||
14164            gameInfo.variant == VariantXiangqi  ||
14165            gameInfo.variant == VariantCourier  ||
14166            gameInfo.variant == VariantMakruk     )
14167             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14168         goto defaultlabel;
14169
14170       case WhiteKing:
14171       case BlackKing:
14172         if(gameInfo.variant == VariantXiangqi)
14173             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14174         if(gameInfo.variant == VariantKnightmate)
14175             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14176       default:
14177         defaultlabel:
14178         if (gameMode == IcsExamining) {
14179             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14180             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14181                      PieceToChar(selection), AAA + x, ONE + y);
14182             SendToICS(buf);
14183         } else {
14184             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14185                 int n;
14186                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14187                     n = PieceToNumber(selection - BlackPawn);
14188                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14189                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14190                     boards[0][BOARD_HEIGHT-1-n][1]++;
14191                 } else
14192                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14193                     n = PieceToNumber(selection);
14194                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14195                     boards[0][n][BOARD_WIDTH-1] = selection;
14196                     boards[0][n][BOARD_WIDTH-2]++;
14197                 }
14198             } else
14199             boards[0][y][x] = selection;
14200             DrawPosition(TRUE, boards[0]);
14201             ClearHighlights();
14202             fromX = fromY = -1;
14203         }
14204         break;
14205     }
14206 }
14207
14208
14209 void
14210 DropMenuEvent (ChessSquare selection, int x, int y)
14211 {
14212     ChessMove moveType;
14213
14214     switch (gameMode) {
14215       case IcsPlayingWhite:
14216       case MachinePlaysBlack:
14217         if (!WhiteOnMove(currentMove)) {
14218             DisplayMoveError(_("It is Black's turn"));
14219             return;
14220         }
14221         moveType = WhiteDrop;
14222         break;
14223       case IcsPlayingBlack:
14224       case MachinePlaysWhite:
14225         if (WhiteOnMove(currentMove)) {
14226             DisplayMoveError(_("It is White's turn"));
14227             return;
14228         }
14229         moveType = BlackDrop;
14230         break;
14231       case EditGame:
14232         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14233         break;
14234       default:
14235         return;
14236     }
14237
14238     if (moveType == BlackDrop && selection < BlackPawn) {
14239       selection = (ChessSquare) ((int) selection
14240                                  + (int) BlackPawn - (int) WhitePawn);
14241     }
14242     if (boards[currentMove][y][x] != EmptySquare) {
14243         DisplayMoveError(_("That square is occupied"));
14244         return;
14245     }
14246
14247     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14248 }
14249
14250 void
14251 AcceptEvent ()
14252 {
14253     /* Accept a pending offer of any kind from opponent */
14254
14255     if (appData.icsActive) {
14256         SendToICS(ics_prefix);
14257         SendToICS("accept\n");
14258     } else if (cmailMsgLoaded) {
14259         if (currentMove == cmailOldMove &&
14260             commentList[cmailOldMove] != NULL &&
14261             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14262                    "Black offers a draw" : "White offers a draw")) {
14263             TruncateGame();
14264             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14265             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14266         } else {
14267             DisplayError(_("There is no pending offer on this move"), 0);
14268             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14269         }
14270     } else {
14271         /* Not used for offers from chess program */
14272     }
14273 }
14274
14275 void
14276 DeclineEvent ()
14277 {
14278     /* Decline a pending offer of any kind from opponent */
14279
14280     if (appData.icsActive) {
14281         SendToICS(ics_prefix);
14282         SendToICS("decline\n");
14283     } else if (cmailMsgLoaded) {
14284         if (currentMove == cmailOldMove &&
14285             commentList[cmailOldMove] != NULL &&
14286             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14287                    "Black offers a draw" : "White offers a draw")) {
14288 #ifdef NOTDEF
14289             AppendComment(cmailOldMove, "Draw declined", TRUE);
14290             DisplayComment(cmailOldMove - 1, "Draw declined");
14291 #endif /*NOTDEF*/
14292         } else {
14293             DisplayError(_("There is no pending offer on this move"), 0);
14294         }
14295     } else {
14296         /* Not used for offers from chess program */
14297     }
14298 }
14299
14300 void
14301 RematchEvent ()
14302 {
14303     /* Issue ICS rematch command */
14304     if (appData.icsActive) {
14305         SendToICS(ics_prefix);
14306         SendToICS("rematch\n");
14307     }
14308 }
14309
14310 void
14311 CallFlagEvent ()
14312 {
14313     /* Call your opponent's flag (claim a win on time) */
14314     if (appData.icsActive) {
14315         SendToICS(ics_prefix);
14316         SendToICS("flag\n");
14317     } else {
14318         switch (gameMode) {
14319           default:
14320             return;
14321           case MachinePlaysWhite:
14322             if (whiteFlag) {
14323                 if (blackFlag)
14324                   GameEnds(GameIsDrawn, "Both players ran out of time",
14325                            GE_PLAYER);
14326                 else
14327                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14328             } else {
14329                 DisplayError(_("Your opponent is not out of time"), 0);
14330             }
14331             break;
14332           case MachinePlaysBlack:
14333             if (blackFlag) {
14334                 if (whiteFlag)
14335                   GameEnds(GameIsDrawn, "Both players ran out of time",
14336                            GE_PLAYER);
14337                 else
14338                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14339             } else {
14340                 DisplayError(_("Your opponent is not out of time"), 0);
14341             }
14342             break;
14343         }
14344     }
14345 }
14346
14347 void
14348 ClockClick (int which)
14349 {       // [HGM] code moved to back-end from winboard.c
14350         if(which) { // black clock
14351           if (gameMode == EditPosition || gameMode == IcsExamining) {
14352             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14353             SetBlackToPlayEvent();
14354           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14355           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14356           } else if (shiftKey) {
14357             AdjustClock(which, -1);
14358           } else if (gameMode == IcsPlayingWhite ||
14359                      gameMode == MachinePlaysBlack) {
14360             CallFlagEvent();
14361           }
14362         } else { // white clock
14363           if (gameMode == EditPosition || gameMode == IcsExamining) {
14364             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14365             SetWhiteToPlayEvent();
14366           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14367           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14368           } else if (shiftKey) {
14369             AdjustClock(which, -1);
14370           } else if (gameMode == IcsPlayingBlack ||
14371                    gameMode == MachinePlaysWhite) {
14372             CallFlagEvent();
14373           }
14374         }
14375 }
14376
14377 void
14378 DrawEvent ()
14379 {
14380     /* Offer draw or accept pending draw offer from opponent */
14381
14382     if (appData.icsActive) {
14383         /* Note: tournament rules require draw offers to be
14384            made after you make your move but before you punch
14385            your clock.  Currently ICS doesn't let you do that;
14386            instead, you immediately punch your clock after making
14387            a move, but you can offer a draw at any time. */
14388
14389         SendToICS(ics_prefix);
14390         SendToICS("draw\n");
14391         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14392     } else if (cmailMsgLoaded) {
14393         if (currentMove == cmailOldMove &&
14394             commentList[cmailOldMove] != NULL &&
14395             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14396                    "Black offers a draw" : "White offers a draw")) {
14397             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14398             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14399         } else if (currentMove == cmailOldMove + 1) {
14400             char *offer = WhiteOnMove(cmailOldMove) ?
14401               "White offers a draw" : "Black offers a draw";
14402             AppendComment(currentMove, offer, TRUE);
14403             DisplayComment(currentMove - 1, offer);
14404             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14405         } else {
14406             DisplayError(_("You must make your move before offering a draw"), 0);
14407             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14408         }
14409     } else if (first.offeredDraw) {
14410         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14411     } else {
14412         if (first.sendDrawOffers) {
14413             SendToProgram("draw\n", &first);
14414             userOfferedDraw = TRUE;
14415         }
14416     }
14417 }
14418
14419 void
14420 AdjournEvent ()
14421 {
14422     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14423
14424     if (appData.icsActive) {
14425         SendToICS(ics_prefix);
14426         SendToICS("adjourn\n");
14427     } else {
14428         /* Currently GNU Chess doesn't offer or accept Adjourns */
14429     }
14430 }
14431
14432
14433 void
14434 AbortEvent ()
14435 {
14436     /* Offer Abort or accept pending Abort offer from opponent */
14437
14438     if (appData.icsActive) {
14439         SendToICS(ics_prefix);
14440         SendToICS("abort\n");
14441     } else {
14442         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14443     }
14444 }
14445
14446 void
14447 ResignEvent ()
14448 {
14449     /* Resign.  You can do this even if it's not your turn. */
14450
14451     if (appData.icsActive) {
14452         SendToICS(ics_prefix);
14453         SendToICS("resign\n");
14454     } else {
14455         switch (gameMode) {
14456           case MachinePlaysWhite:
14457             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14458             break;
14459           case MachinePlaysBlack:
14460             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14461             break;
14462           case EditGame:
14463             if (cmailMsgLoaded) {
14464                 TruncateGame();
14465                 if (WhiteOnMove(cmailOldMove)) {
14466                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14467                 } else {
14468                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14469                 }
14470                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14471             }
14472             break;
14473           default:
14474             break;
14475         }
14476     }
14477 }
14478
14479
14480 void
14481 StopObservingEvent ()
14482 {
14483     /* Stop observing current games */
14484     SendToICS(ics_prefix);
14485     SendToICS("unobserve\n");
14486 }
14487
14488 void
14489 StopExaminingEvent ()
14490 {
14491     /* Stop observing current game */
14492     SendToICS(ics_prefix);
14493     SendToICS("unexamine\n");
14494 }
14495
14496 void
14497 ForwardInner (int target)
14498 {
14499     int limit; int oldSeekGraphUp = seekGraphUp;
14500
14501     if (appData.debugMode)
14502         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14503                 target, currentMove, forwardMostMove);
14504
14505     if (gameMode == EditPosition)
14506       return;
14507
14508     seekGraphUp = FALSE;
14509     MarkTargetSquares(1);
14510
14511     if (gameMode == PlayFromGameFile && !pausing)
14512       PauseEvent();
14513
14514     if (gameMode == IcsExamining && pausing)
14515       limit = pauseExamForwardMostMove;
14516     else
14517       limit = forwardMostMove;
14518
14519     if (target > limit) target = limit;
14520
14521     if (target > 0 && moveList[target - 1][0]) {
14522         int fromX, fromY, toX, toY;
14523         toX = moveList[target - 1][2] - AAA;
14524         toY = moveList[target - 1][3] - ONE;
14525         if (moveList[target - 1][1] == '@') {
14526             if (appData.highlightLastMove) {
14527                 SetHighlights(-1, -1, toX, toY);
14528             }
14529         } else {
14530             fromX = moveList[target - 1][0] - AAA;
14531             fromY = moveList[target - 1][1] - ONE;
14532             if (target == currentMove + 1) {
14533                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14534             }
14535             if (appData.highlightLastMove) {
14536                 SetHighlights(fromX, fromY, toX, toY);
14537             }
14538         }
14539     }
14540     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14541         gameMode == Training || gameMode == PlayFromGameFile ||
14542         gameMode == AnalyzeFile) {
14543         while (currentMove < target) {
14544             SendMoveToProgram(currentMove++, &first);
14545         }
14546     } else {
14547         currentMove = target;
14548     }
14549
14550     if (gameMode == EditGame || gameMode == EndOfGame) {
14551         whiteTimeRemaining = timeRemaining[0][currentMove];
14552         blackTimeRemaining = timeRemaining[1][currentMove];
14553     }
14554     DisplayBothClocks();
14555     DisplayMove(currentMove - 1);
14556     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14557     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14558     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14559         DisplayComment(currentMove - 1, commentList[currentMove]);
14560     }
14561     ClearMap(); // [HGM] exclude: invalidate map
14562 }
14563
14564
14565 void
14566 ForwardEvent ()
14567 {
14568     if (gameMode == IcsExamining && !pausing) {
14569         SendToICS(ics_prefix);
14570         SendToICS("forward\n");
14571     } else {
14572         ForwardInner(currentMove + 1);
14573     }
14574 }
14575
14576 void
14577 ToEndEvent ()
14578 {
14579     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14580         /* to optimze, we temporarily turn off analysis mode while we feed
14581          * the remaining moves to the engine. Otherwise we get analysis output
14582          * after each move.
14583          */
14584         if (first.analysisSupport) {
14585           SendToProgram("exit\nforce\n", &first);
14586           first.analyzing = FALSE;
14587         }
14588     }
14589
14590     if (gameMode == IcsExamining && !pausing) {
14591         SendToICS(ics_prefix);
14592         SendToICS("forward 999999\n");
14593     } else {
14594         ForwardInner(forwardMostMove);
14595     }
14596
14597     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14598         /* we have fed all the moves, so reactivate analysis mode */
14599         SendToProgram("analyze\n", &first);
14600         first.analyzing = TRUE;
14601         /*first.maybeThinking = TRUE;*/
14602         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14603     }
14604 }
14605
14606 void
14607 BackwardInner (int target)
14608 {
14609     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14610
14611     if (appData.debugMode)
14612         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14613                 target, currentMove, forwardMostMove);
14614
14615     if (gameMode == EditPosition) return;
14616     seekGraphUp = FALSE;
14617     MarkTargetSquares(1);
14618     if (currentMove <= backwardMostMove) {
14619         ClearHighlights();
14620         DrawPosition(full_redraw, boards[currentMove]);
14621         return;
14622     }
14623     if (gameMode == PlayFromGameFile && !pausing)
14624       PauseEvent();
14625
14626     if (moveList[target][0]) {
14627         int fromX, fromY, toX, toY;
14628         toX = moveList[target][2] - AAA;
14629         toY = moveList[target][3] - ONE;
14630         if (moveList[target][1] == '@') {
14631             if (appData.highlightLastMove) {
14632                 SetHighlights(-1, -1, toX, toY);
14633             }
14634         } else {
14635             fromX = moveList[target][0] - AAA;
14636             fromY = moveList[target][1] - ONE;
14637             if (target == currentMove - 1) {
14638                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14639             }
14640             if (appData.highlightLastMove) {
14641                 SetHighlights(fromX, fromY, toX, toY);
14642             }
14643         }
14644     }
14645     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14646         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14647         while (currentMove > target) {
14648             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14649                 // null move cannot be undone. Reload program with move history before it.
14650                 int i;
14651                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14652                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14653                 }
14654                 SendBoard(&first, i); 
14655                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14656                 break;
14657             }
14658             SendToProgram("undo\n", &first);
14659             currentMove--;
14660         }
14661     } else {
14662         currentMove = target;
14663     }
14664
14665     if (gameMode == EditGame || gameMode == EndOfGame) {
14666         whiteTimeRemaining = timeRemaining[0][currentMove];
14667         blackTimeRemaining = timeRemaining[1][currentMove];
14668     }
14669     DisplayBothClocks();
14670     DisplayMove(currentMove - 1);
14671     DrawPosition(full_redraw, boards[currentMove]);
14672     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14673     // [HGM] PV info: routine tests if comment empty
14674     DisplayComment(currentMove - 1, commentList[currentMove]);
14675     ClearMap(); // [HGM] exclude: invalidate map
14676 }
14677
14678 void
14679 BackwardEvent ()
14680 {
14681     if (gameMode == IcsExamining && !pausing) {
14682         SendToICS(ics_prefix);
14683         SendToICS("backward\n");
14684     } else {
14685         BackwardInner(currentMove - 1);
14686     }
14687 }
14688
14689 void
14690 ToStartEvent ()
14691 {
14692     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14693         /* to optimize, we temporarily turn off analysis mode while we undo
14694          * all the moves. Otherwise we get analysis output after each undo.
14695          */
14696         if (first.analysisSupport) {
14697           SendToProgram("exit\nforce\n", &first);
14698           first.analyzing = FALSE;
14699         }
14700     }
14701
14702     if (gameMode == IcsExamining && !pausing) {
14703         SendToICS(ics_prefix);
14704         SendToICS("backward 999999\n");
14705     } else {
14706         BackwardInner(backwardMostMove);
14707     }
14708
14709     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14710         /* we have fed all the moves, so reactivate analysis mode */
14711         SendToProgram("analyze\n", &first);
14712         first.analyzing = TRUE;
14713         /*first.maybeThinking = TRUE;*/
14714         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14715     }
14716 }
14717
14718 void
14719 ToNrEvent (int to)
14720 {
14721   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14722   if (to >= forwardMostMove) to = forwardMostMove;
14723   if (to <= backwardMostMove) to = backwardMostMove;
14724   if (to < currentMove) {
14725     BackwardInner(to);
14726   } else {
14727     ForwardInner(to);
14728   }
14729 }
14730
14731 void
14732 RevertEvent (Boolean annotate)
14733 {
14734     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14735         return;
14736     }
14737     if (gameMode != IcsExamining) {
14738         DisplayError(_("You are not examining a game"), 0);
14739         return;
14740     }
14741     if (pausing) {
14742         DisplayError(_("You can't revert while pausing"), 0);
14743         return;
14744     }
14745     SendToICS(ics_prefix);
14746     SendToICS("revert\n");
14747 }
14748
14749 void
14750 RetractMoveEvent ()
14751 {
14752     switch (gameMode) {
14753       case MachinePlaysWhite:
14754       case MachinePlaysBlack:
14755         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14756             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14757             return;
14758         }
14759         if (forwardMostMove < 2) return;
14760         currentMove = forwardMostMove = forwardMostMove - 2;
14761         whiteTimeRemaining = timeRemaining[0][currentMove];
14762         blackTimeRemaining = timeRemaining[1][currentMove];
14763         DisplayBothClocks();
14764         DisplayMove(currentMove - 1);
14765         ClearHighlights();/*!! could figure this out*/
14766         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14767         SendToProgram("remove\n", &first);
14768         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14769         break;
14770
14771       case BeginningOfGame:
14772       default:
14773         break;
14774
14775       case IcsPlayingWhite:
14776       case IcsPlayingBlack:
14777         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14778             SendToICS(ics_prefix);
14779             SendToICS("takeback 2\n");
14780         } else {
14781             SendToICS(ics_prefix);
14782             SendToICS("takeback 1\n");
14783         }
14784         break;
14785     }
14786 }
14787
14788 void
14789 MoveNowEvent ()
14790 {
14791     ChessProgramState *cps;
14792
14793     switch (gameMode) {
14794       case MachinePlaysWhite:
14795         if (!WhiteOnMove(forwardMostMove)) {
14796             DisplayError(_("It is your turn"), 0);
14797             return;
14798         }
14799         cps = &first;
14800         break;
14801       case MachinePlaysBlack:
14802         if (WhiteOnMove(forwardMostMove)) {
14803             DisplayError(_("It is your turn"), 0);
14804             return;
14805         }
14806         cps = &first;
14807         break;
14808       case TwoMachinesPlay:
14809         if (WhiteOnMove(forwardMostMove) ==
14810             (first.twoMachinesColor[0] == 'w')) {
14811             cps = &first;
14812         } else {
14813             cps = &second;
14814         }
14815         break;
14816       case BeginningOfGame:
14817       default:
14818         return;
14819     }
14820     SendToProgram("?\n", cps);
14821 }
14822
14823 void
14824 TruncateGameEvent ()
14825 {
14826     EditGameEvent();
14827     if (gameMode != EditGame) return;
14828     TruncateGame();
14829 }
14830
14831 void
14832 TruncateGame ()
14833 {
14834     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14835     if (forwardMostMove > currentMove) {
14836         if (gameInfo.resultDetails != NULL) {
14837             free(gameInfo.resultDetails);
14838             gameInfo.resultDetails = NULL;
14839             gameInfo.result = GameUnfinished;
14840         }
14841         forwardMostMove = currentMove;
14842         HistorySet(parseList, backwardMostMove, forwardMostMove,
14843                    currentMove-1);
14844     }
14845 }
14846
14847 void
14848 HintEvent ()
14849 {
14850     if (appData.noChessProgram) return;
14851     switch (gameMode) {
14852       case MachinePlaysWhite:
14853         if (WhiteOnMove(forwardMostMove)) {
14854             DisplayError(_("Wait until your turn"), 0);
14855             return;
14856         }
14857         break;
14858       case BeginningOfGame:
14859       case MachinePlaysBlack:
14860         if (!WhiteOnMove(forwardMostMove)) {
14861             DisplayError(_("Wait until your turn"), 0);
14862             return;
14863         }
14864         break;
14865       default:
14866         DisplayError(_("No hint available"), 0);
14867         return;
14868     }
14869     SendToProgram("hint\n", &first);
14870     hintRequested = TRUE;
14871 }
14872
14873 void
14874 BookEvent ()
14875 {
14876     if (appData.noChessProgram) return;
14877     switch (gameMode) {
14878       case MachinePlaysWhite:
14879         if (WhiteOnMove(forwardMostMove)) {
14880             DisplayError(_("Wait until your turn"), 0);
14881             return;
14882         }
14883         break;
14884       case BeginningOfGame:
14885       case MachinePlaysBlack:
14886         if (!WhiteOnMove(forwardMostMove)) {
14887             DisplayError(_("Wait until your turn"), 0);
14888             return;
14889         }
14890         break;
14891       case EditPosition:
14892         EditPositionDone(TRUE);
14893         break;
14894       case TwoMachinesPlay:
14895         return;
14896       default:
14897         break;
14898     }
14899     SendToProgram("bk\n", &first);
14900     bookOutput[0] = NULLCHAR;
14901     bookRequested = TRUE;
14902 }
14903
14904 void
14905 AboutGameEvent ()
14906 {
14907     char *tags = PGNTags(&gameInfo);
14908     TagsPopUp(tags, CmailMsg());
14909     free(tags);
14910 }
14911
14912 /* end button procedures */
14913
14914 void
14915 PrintPosition (FILE *fp, int move)
14916 {
14917     int i, j;
14918
14919     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14920         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14921             char c = PieceToChar(boards[move][i][j]);
14922             fputc(c == 'x' ? '.' : c, fp);
14923             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14924         }
14925     }
14926     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14927       fprintf(fp, "white to play\n");
14928     else
14929       fprintf(fp, "black to play\n");
14930 }
14931
14932 void
14933 PrintOpponents (FILE *fp)
14934 {
14935     if (gameInfo.white != NULL) {
14936         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14937     } else {
14938         fprintf(fp, "\n");
14939     }
14940 }
14941
14942 /* Find last component of program's own name, using some heuristics */
14943 void
14944 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14945 {
14946     char *p, *q, c;
14947     int local = (strcmp(host, "localhost") == 0);
14948     while (!local && (p = strchr(prog, ';')) != NULL) {
14949         p++;
14950         while (*p == ' ') p++;
14951         prog = p;
14952     }
14953     if (*prog == '"' || *prog == '\'') {
14954         q = strchr(prog + 1, *prog);
14955     } else {
14956         q = strchr(prog, ' ');
14957     }
14958     if (q == NULL) q = prog + strlen(prog);
14959     p = q;
14960     while (p >= prog && *p != '/' && *p != '\\') p--;
14961     p++;
14962     if(p == prog && *p == '"') p++;
14963     c = *q; *q = 0;
14964     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14965     memcpy(buf, p, q - p);
14966     buf[q - p] = NULLCHAR;
14967     if (!local) {
14968         strcat(buf, "@");
14969         strcat(buf, host);
14970     }
14971 }
14972
14973 char *
14974 TimeControlTagValue ()
14975 {
14976     char buf[MSG_SIZ];
14977     if (!appData.clockMode) {
14978       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14979     } else if (movesPerSession > 0) {
14980       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14981     } else if (timeIncrement == 0) {
14982       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14983     } else {
14984       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14985     }
14986     return StrSave(buf);
14987 }
14988
14989 void
14990 SetGameInfo ()
14991 {
14992     /* This routine is used only for certain modes */
14993     VariantClass v = gameInfo.variant;
14994     ChessMove r = GameUnfinished;
14995     char *p = NULL;
14996
14997     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14998         r = gameInfo.result;
14999         p = gameInfo.resultDetails;
15000         gameInfo.resultDetails = NULL;
15001     }
15002     ClearGameInfo(&gameInfo);
15003     gameInfo.variant = v;
15004
15005     switch (gameMode) {
15006       case MachinePlaysWhite:
15007         gameInfo.event = StrSave( appData.pgnEventHeader );
15008         gameInfo.site = StrSave(HostName());
15009         gameInfo.date = PGNDate();
15010         gameInfo.round = StrSave("-");
15011         gameInfo.white = StrSave(first.tidy);
15012         gameInfo.black = StrSave(UserName());
15013         gameInfo.timeControl = TimeControlTagValue();
15014         break;
15015
15016       case MachinePlaysBlack:
15017         gameInfo.event = StrSave( appData.pgnEventHeader );
15018         gameInfo.site = StrSave(HostName());
15019         gameInfo.date = PGNDate();
15020         gameInfo.round = StrSave("-");
15021         gameInfo.white = StrSave(UserName());
15022         gameInfo.black = StrSave(first.tidy);
15023         gameInfo.timeControl = TimeControlTagValue();
15024         break;
15025
15026       case TwoMachinesPlay:
15027         gameInfo.event = StrSave( appData.pgnEventHeader );
15028         gameInfo.site = StrSave(HostName());
15029         gameInfo.date = PGNDate();
15030         if (roundNr > 0) {
15031             char buf[MSG_SIZ];
15032             snprintf(buf, MSG_SIZ, "%d", roundNr);
15033             gameInfo.round = StrSave(buf);
15034         } else {
15035             gameInfo.round = StrSave("-");
15036         }
15037         if (first.twoMachinesColor[0] == 'w') {
15038             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15039             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15040         } else {
15041             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15042             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15043         }
15044         gameInfo.timeControl = TimeControlTagValue();
15045         break;
15046
15047       case EditGame:
15048         gameInfo.event = StrSave("Edited game");
15049         gameInfo.site = StrSave(HostName());
15050         gameInfo.date = PGNDate();
15051         gameInfo.round = StrSave("-");
15052         gameInfo.white = StrSave("-");
15053         gameInfo.black = StrSave("-");
15054         gameInfo.result = r;
15055         gameInfo.resultDetails = p;
15056         break;
15057
15058       case EditPosition:
15059         gameInfo.event = StrSave("Edited position");
15060         gameInfo.site = StrSave(HostName());
15061         gameInfo.date = PGNDate();
15062         gameInfo.round = StrSave("-");
15063         gameInfo.white = StrSave("-");
15064         gameInfo.black = StrSave("-");
15065         break;
15066
15067       case IcsPlayingWhite:
15068       case IcsPlayingBlack:
15069       case IcsObserving:
15070       case IcsExamining:
15071         break;
15072
15073       case PlayFromGameFile:
15074         gameInfo.event = StrSave("Game from non-PGN file");
15075         gameInfo.site = StrSave(HostName());
15076         gameInfo.date = PGNDate();
15077         gameInfo.round = StrSave("-");
15078         gameInfo.white = StrSave("?");
15079         gameInfo.black = StrSave("?");
15080         break;
15081
15082       default:
15083         break;
15084     }
15085 }
15086
15087 void
15088 ReplaceComment (int index, char *text)
15089 {
15090     int len;
15091     char *p;
15092     float score;
15093
15094     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15095        pvInfoList[index-1].depth == len &&
15096        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15097        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15098     while (*text == '\n') text++;
15099     len = strlen(text);
15100     while (len > 0 && text[len - 1] == '\n') len--;
15101
15102     if (commentList[index] != NULL)
15103       free(commentList[index]);
15104
15105     if (len == 0) {
15106         commentList[index] = NULL;
15107         return;
15108     }
15109   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15110       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15111       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15112     commentList[index] = (char *) malloc(len + 2);
15113     strncpy(commentList[index], text, len);
15114     commentList[index][len] = '\n';
15115     commentList[index][len + 1] = NULLCHAR;
15116   } else {
15117     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15118     char *p;
15119     commentList[index] = (char *) malloc(len + 7);
15120     safeStrCpy(commentList[index], "{\n", 3);
15121     safeStrCpy(commentList[index]+2, text, len+1);
15122     commentList[index][len+2] = NULLCHAR;
15123     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15124     strcat(commentList[index], "\n}\n");
15125   }
15126 }
15127
15128 void
15129 CrushCRs (char *text)
15130 {
15131   char *p = text;
15132   char *q = text;
15133   char ch;
15134
15135   do {
15136     ch = *p++;
15137     if (ch == '\r') continue;
15138     *q++ = ch;
15139   } while (ch != '\0');
15140 }
15141
15142 void
15143 AppendComment (int index, char *text, Boolean addBraces)
15144 /* addBraces  tells if we should add {} */
15145 {
15146     int oldlen, len;
15147     char *old;
15148
15149 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15150     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15151
15152     CrushCRs(text);
15153     while (*text == '\n') text++;
15154     len = strlen(text);
15155     while (len > 0 && text[len - 1] == '\n') len--;
15156     text[len] = NULLCHAR;
15157
15158     if (len == 0) return;
15159
15160     if (commentList[index] != NULL) {
15161       Boolean addClosingBrace = addBraces;
15162         old = commentList[index];
15163         oldlen = strlen(old);
15164         while(commentList[index][oldlen-1] ==  '\n')
15165           commentList[index][--oldlen] = NULLCHAR;
15166         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15167         safeStrCpy(commentList[index], old, oldlen + len + 6);
15168         free(old);
15169         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15170         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15171           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15172           while (*text == '\n') { text++; len--; }
15173           commentList[index][--oldlen] = NULLCHAR;
15174       }
15175         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15176         else          strcat(commentList[index], "\n");
15177         strcat(commentList[index], text);
15178         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15179         else          strcat(commentList[index], "\n");
15180     } else {
15181         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15182         if(addBraces)
15183           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15184         else commentList[index][0] = NULLCHAR;
15185         strcat(commentList[index], text);
15186         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15187         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15188     }
15189 }
15190
15191 static char *
15192 FindStr (char * text, char * sub_text)
15193 {
15194     char * result = strstr( text, sub_text );
15195
15196     if( result != NULL ) {
15197         result += strlen( sub_text );
15198     }
15199
15200     return result;
15201 }
15202
15203 /* [AS] Try to extract PV info from PGN comment */
15204 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15205 char *
15206 GetInfoFromComment (int index, char * text)
15207 {
15208     char * sep = text, *p;
15209
15210     if( text != NULL && index > 0 ) {
15211         int score = 0;
15212         int depth = 0;
15213         int time = -1, sec = 0, deci;
15214         char * s_eval = FindStr( text, "[%eval " );
15215         char * s_emt = FindStr( text, "[%emt " );
15216
15217         if( s_eval != NULL || s_emt != NULL ) {
15218             /* New style */
15219             char delim;
15220
15221             if( s_eval != NULL ) {
15222                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15223                     return text;
15224                 }
15225
15226                 if( delim != ']' ) {
15227                     return text;
15228                 }
15229             }
15230
15231             if( s_emt != NULL ) {
15232             }
15233                 return text;
15234         }
15235         else {
15236             /* We expect something like: [+|-]nnn.nn/dd */
15237             int score_lo = 0;
15238
15239             if(*text != '{') return text; // [HGM] braces: must be normal comment
15240
15241             sep = strchr( text, '/' );
15242             if( sep == NULL || sep < (text+4) ) {
15243                 return text;
15244             }
15245
15246             p = text;
15247             if(p[1] == '(') { // comment starts with PV
15248                p = strchr(p, ')'); // locate end of PV
15249                if(p == NULL || sep < p+5) return text;
15250                // at this point we have something like "{(.*) +0.23/6 ..."
15251                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15252                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15253                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15254             }
15255             time = -1; sec = -1; deci = -1;
15256             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15257                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15258                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15259                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15260                 return text;
15261             }
15262
15263             if( score_lo < 0 || score_lo >= 100 ) {
15264                 return text;
15265             }
15266
15267             if(sec >= 0) time = 600*time + 10*sec; else
15268             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15269
15270             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15271
15272             /* [HGM] PV time: now locate end of PV info */
15273             while( *++sep >= '0' && *sep <= '9'); // strip depth
15274             if(time >= 0)
15275             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15276             if(sec >= 0)
15277             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15278             if(deci >= 0)
15279             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15280             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15281         }
15282
15283         if( depth <= 0 ) {
15284             return text;
15285         }
15286
15287         if( time < 0 ) {
15288             time = -1;
15289         }
15290
15291         pvInfoList[index-1].depth = depth;
15292         pvInfoList[index-1].score = score;
15293         pvInfoList[index-1].time  = 10*time; // centi-sec
15294         if(*sep == '}') *sep = 0; else *--sep = '{';
15295         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15296     }
15297     return sep;
15298 }
15299
15300 void
15301 SendToProgram (char *message, ChessProgramState *cps)
15302 {
15303     int count, outCount, error;
15304     char buf[MSG_SIZ];
15305
15306     if (cps->pr == NoProc) return;
15307     Attention(cps);
15308
15309     if (appData.debugMode) {
15310         TimeMark now;
15311         GetTimeMark(&now);
15312         fprintf(debugFP, "%ld >%-6s: %s",
15313                 SubtractTimeMarks(&now, &programStartTime),
15314                 cps->which, message);
15315         if(serverFP)
15316             fprintf(serverFP, "%ld >%-6s: %s",
15317                 SubtractTimeMarks(&now, &programStartTime),
15318                 cps->which, message), fflush(serverFP);
15319     }
15320
15321     count = strlen(message);
15322     outCount = OutputToProcess(cps->pr, message, count, &error);
15323     if (outCount < count && !exiting
15324                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15325       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15326       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15327         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15328             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15329                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15330                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15331                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15332             } else {
15333                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15334                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15335                 gameInfo.result = res;
15336             }
15337             gameInfo.resultDetails = StrSave(buf);
15338         }
15339         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15340         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15341     }
15342 }
15343
15344 void
15345 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15346 {
15347     char *end_str;
15348     char buf[MSG_SIZ];
15349     ChessProgramState *cps = (ChessProgramState *)closure;
15350
15351     if (isr != cps->isr) return; /* Killed intentionally */
15352     if (count <= 0) {
15353         if (count == 0) {
15354             RemoveInputSource(cps->isr);
15355             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15356                     _(cps->which), cps->program);
15357             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15358             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15359                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15360                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15361                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15362                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15363                 } else {
15364                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15365                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15366                     gameInfo.result = res;
15367                 }
15368                 gameInfo.resultDetails = StrSave(buf);
15369             }
15370             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15371             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15372         } else {
15373             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15374                     _(cps->which), cps->program);
15375             RemoveInputSource(cps->isr);
15376
15377             /* [AS] Program is misbehaving badly... kill it */
15378             if( count == -2 ) {
15379                 DestroyChildProcess( cps->pr, 9 );
15380                 cps->pr = NoProc;
15381             }
15382
15383             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15384         }
15385         return;
15386     }
15387
15388     if ((end_str = strchr(message, '\r')) != NULL)
15389       *end_str = NULLCHAR;
15390     if ((end_str = strchr(message, '\n')) != NULL)
15391       *end_str = NULLCHAR;
15392
15393     if (appData.debugMode) {
15394         TimeMark now; int print = 1;
15395         char *quote = ""; char c; int i;
15396
15397         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15398                 char start = message[0];
15399                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15400                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15401                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15402                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15403                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15404                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15405                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15406                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15407                    sscanf(message, "hint: %c", &c)!=1 && 
15408                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15409                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15410                     print = (appData.engineComments >= 2);
15411                 }
15412                 message[0] = start; // restore original message
15413         }
15414         if(print) {
15415                 GetTimeMark(&now);
15416                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15417                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15418                         quote,
15419                         message);
15420                 if(serverFP)
15421                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15422                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15423                         quote,
15424                         message), fflush(serverFP);
15425         }
15426     }
15427
15428     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15429     if (appData.icsEngineAnalyze) {
15430         if (strstr(message, "whisper") != NULL ||
15431              strstr(message, "kibitz") != NULL ||
15432             strstr(message, "tellics") != NULL) return;
15433     }
15434
15435     HandleMachineMove(message, cps);
15436 }
15437
15438
15439 void
15440 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15441 {
15442     char buf[MSG_SIZ];
15443     int seconds;
15444
15445     if( timeControl_2 > 0 ) {
15446         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15447             tc = timeControl_2;
15448         }
15449     }
15450     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15451     inc /= cps->timeOdds;
15452     st  /= cps->timeOdds;
15453
15454     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15455
15456     if (st > 0) {
15457       /* Set exact time per move, normally using st command */
15458       if (cps->stKludge) {
15459         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15460         seconds = st % 60;
15461         if (seconds == 0) {
15462           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15463         } else {
15464           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15465         }
15466       } else {
15467         snprintf(buf, MSG_SIZ, "st %d\n", st);
15468       }
15469     } else {
15470       /* Set conventional or incremental time control, using level command */
15471       if (seconds == 0) {
15472         /* Note old gnuchess bug -- minutes:seconds used to not work.
15473            Fixed in later versions, but still avoid :seconds
15474            when seconds is 0. */
15475         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15476       } else {
15477         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15478                  seconds, inc/1000.);
15479       }
15480     }
15481     SendToProgram(buf, cps);
15482
15483     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15484     /* Orthogonally, limit search to given depth */
15485     if (sd > 0) {
15486       if (cps->sdKludge) {
15487         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15488       } else {
15489         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15490       }
15491       SendToProgram(buf, cps);
15492     }
15493
15494     if(cps->nps >= 0) { /* [HGM] nps */
15495         if(cps->supportsNPS == FALSE)
15496           cps->nps = -1; // don't use if engine explicitly says not supported!
15497         else {
15498           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15499           SendToProgram(buf, cps);
15500         }
15501     }
15502 }
15503
15504 ChessProgramState *
15505 WhitePlayer ()
15506 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15507 {
15508     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15509        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15510         return &second;
15511     return &first;
15512 }
15513
15514 void
15515 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15516 {
15517     char message[MSG_SIZ];
15518     long time, otime;
15519
15520     /* Note: this routine must be called when the clocks are stopped
15521        or when they have *just* been set or switched; otherwise
15522        it will be off by the time since the current tick started.
15523     */
15524     if (machineWhite) {
15525         time = whiteTimeRemaining / 10;
15526         otime = blackTimeRemaining / 10;
15527     } else {
15528         time = blackTimeRemaining / 10;
15529         otime = whiteTimeRemaining / 10;
15530     }
15531     /* [HGM] translate opponent's time by time-odds factor */
15532     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15533
15534     if (time <= 0) time = 1;
15535     if (otime <= 0) otime = 1;
15536
15537     snprintf(message, MSG_SIZ, "time %ld\n", time);
15538     SendToProgram(message, cps);
15539
15540     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15541     SendToProgram(message, cps);
15542 }
15543
15544 int
15545 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15546 {
15547   char buf[MSG_SIZ];
15548   int len = strlen(name);
15549   int val;
15550
15551   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15552     (*p) += len + 1;
15553     sscanf(*p, "%d", &val);
15554     *loc = (val != 0);
15555     while (**p && **p != ' ')
15556       (*p)++;
15557     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15558     SendToProgram(buf, cps);
15559     return TRUE;
15560   }
15561   return FALSE;
15562 }
15563
15564 int
15565 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15566 {
15567   char buf[MSG_SIZ];
15568   int len = strlen(name);
15569   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15570     (*p) += len + 1;
15571     sscanf(*p, "%d", loc);
15572     while (**p && **p != ' ') (*p)++;
15573     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15574     SendToProgram(buf, cps);
15575     return TRUE;
15576   }
15577   return FALSE;
15578 }
15579
15580 int
15581 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15582 {
15583   char buf[MSG_SIZ];
15584   int len = strlen(name);
15585   if (strncmp((*p), name, len) == 0
15586       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15587     (*p) += len + 2;
15588     sscanf(*p, "%[^\"]", loc);
15589     while (**p && **p != '\"') (*p)++;
15590     if (**p == '\"') (*p)++;
15591     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15592     SendToProgram(buf, cps);
15593     return TRUE;
15594   }
15595   return FALSE;
15596 }
15597
15598 int
15599 ParseOption (Option *opt, ChessProgramState *cps)
15600 // [HGM] options: process the string that defines an engine option, and determine
15601 // name, type, default value, and allowed value range
15602 {
15603         char *p, *q, buf[MSG_SIZ];
15604         int n, min = (-1)<<31, max = 1<<31, def;
15605
15606         if(p = strstr(opt->name, " -spin ")) {
15607             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15608             if(max < min) max = min; // enforce consistency
15609             if(def < min) def = min;
15610             if(def > max) def = max;
15611             opt->value = def;
15612             opt->min = min;
15613             opt->max = max;
15614             opt->type = Spin;
15615         } else if((p = strstr(opt->name, " -slider "))) {
15616             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15617             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15618             if(max < min) max = min; // enforce consistency
15619             if(def < min) def = min;
15620             if(def > max) def = max;
15621             opt->value = def;
15622             opt->min = min;
15623             opt->max = max;
15624             opt->type = Spin; // Slider;
15625         } else if((p = strstr(opt->name, " -string "))) {
15626             opt->textValue = p+9;
15627             opt->type = TextBox;
15628         } else if((p = strstr(opt->name, " -file "))) {
15629             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15630             opt->textValue = p+7;
15631             opt->type = FileName; // FileName;
15632         } else if((p = strstr(opt->name, " -path "))) {
15633             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15634             opt->textValue = p+7;
15635             opt->type = PathName; // PathName;
15636         } else if(p = strstr(opt->name, " -check ")) {
15637             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15638             opt->value = (def != 0);
15639             opt->type = CheckBox;
15640         } else if(p = strstr(opt->name, " -combo ")) {
15641             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15642             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15643             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15644             opt->value = n = 0;
15645             while(q = StrStr(q, " /// ")) {
15646                 n++; *q = 0;    // count choices, and null-terminate each of them
15647                 q += 5;
15648                 if(*q == '*') { // remember default, which is marked with * prefix
15649                     q++;
15650                     opt->value = n;
15651                 }
15652                 cps->comboList[cps->comboCnt++] = q;
15653             }
15654             cps->comboList[cps->comboCnt++] = NULL;
15655             opt->max = n + 1;
15656             opt->type = ComboBox;
15657         } else if(p = strstr(opt->name, " -button")) {
15658             opt->type = Button;
15659         } else if(p = strstr(opt->name, " -save")) {
15660             opt->type = SaveButton;
15661         } else return FALSE;
15662         *p = 0; // terminate option name
15663         // now look if the command-line options define a setting for this engine option.
15664         if(cps->optionSettings && cps->optionSettings[0])
15665             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15666         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15667           snprintf(buf, MSG_SIZ, "option %s", p);
15668                 if(p = strstr(buf, ",")) *p = 0;
15669                 if(q = strchr(buf, '=')) switch(opt->type) {
15670                     case ComboBox:
15671                         for(n=0; n<opt->max; n++)
15672                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15673                         break;
15674                     case TextBox:
15675                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15676                         break;
15677                     case Spin:
15678                     case CheckBox:
15679                         opt->value = atoi(q+1);
15680                     default:
15681                         break;
15682                 }
15683                 strcat(buf, "\n");
15684                 SendToProgram(buf, cps);
15685         }
15686         return TRUE;
15687 }
15688
15689 void
15690 FeatureDone (ChessProgramState *cps, int val)
15691 {
15692   DelayedEventCallback cb = GetDelayedEvent();
15693   if ((cb == InitBackEnd3 && cps == &first) ||
15694       (cb == SettingsMenuIfReady && cps == &second) ||
15695       (cb == LoadEngine) ||
15696       (cb == TwoMachinesEventIfReady)) {
15697     CancelDelayedEvent();
15698     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15699   }
15700   cps->initDone = val;
15701 }
15702
15703 /* Parse feature command from engine */
15704 void
15705 ParseFeatures (char *args, ChessProgramState *cps)
15706 {
15707   char *p = args;
15708   char *q;
15709   int val;
15710   char buf[MSG_SIZ];
15711
15712   for (;;) {
15713     while (*p == ' ') p++;
15714     if (*p == NULLCHAR) return;
15715
15716     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15717     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15718     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15719     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15720     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15721     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15722     if (BoolFeature(&p, "reuse", &val, cps)) {
15723       /* Engine can disable reuse, but can't enable it if user said no */
15724       if (!val) cps->reuse = FALSE;
15725       continue;
15726     }
15727     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15728     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15729       if (gameMode == TwoMachinesPlay) {
15730         DisplayTwoMachinesTitle();
15731       } else {
15732         DisplayTitle("");
15733       }
15734       continue;
15735     }
15736     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15737     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15738     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15739     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15740     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15741     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15742     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15743     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15744     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15745     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15746     if (IntFeature(&p, "done", &val, cps)) {
15747       FeatureDone(cps, val);
15748       continue;
15749     }
15750     /* Added by Tord: */
15751     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15752     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15753     /* End of additions by Tord */
15754
15755     /* [HGM] added features: */
15756     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15757     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15758     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15759     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15760     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15761     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15762     if (StringFeature(&p, "option", buf, cps)) {
15763         FREE(cps->option[cps->nrOptions].name);
15764         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15765         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15766         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15767           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15768             SendToProgram(buf, cps);
15769             continue;
15770         }
15771         if(cps->nrOptions >= MAX_OPTIONS) {
15772             cps->nrOptions--;
15773             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15774             DisplayError(buf, 0);
15775         }
15776         continue;
15777     }
15778     /* End of additions by HGM */
15779
15780     /* unknown feature: complain and skip */
15781     q = p;
15782     while (*q && *q != '=') q++;
15783     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15784     SendToProgram(buf, cps);
15785     p = q;
15786     if (*p == '=') {
15787       p++;
15788       if (*p == '\"') {
15789         p++;
15790         while (*p && *p != '\"') p++;
15791         if (*p == '\"') p++;
15792       } else {
15793         while (*p && *p != ' ') p++;
15794       }
15795     }
15796   }
15797
15798 }
15799
15800 void
15801 PeriodicUpdatesEvent (int newState)
15802 {
15803     if (newState == appData.periodicUpdates)
15804       return;
15805
15806     appData.periodicUpdates=newState;
15807
15808     /* Display type changes, so update it now */
15809 //    DisplayAnalysis();
15810
15811     /* Get the ball rolling again... */
15812     if (newState) {
15813         AnalysisPeriodicEvent(1);
15814         StartAnalysisClock();
15815     }
15816 }
15817
15818 void
15819 PonderNextMoveEvent (int newState)
15820 {
15821     if (newState == appData.ponderNextMove) return;
15822     if (gameMode == EditPosition) EditPositionDone(TRUE);
15823     if (newState) {
15824         SendToProgram("hard\n", &first);
15825         if (gameMode == TwoMachinesPlay) {
15826             SendToProgram("hard\n", &second);
15827         }
15828     } else {
15829         SendToProgram("easy\n", &first);
15830         thinkOutput[0] = NULLCHAR;
15831         if (gameMode == TwoMachinesPlay) {
15832             SendToProgram("easy\n", &second);
15833         }
15834     }
15835     appData.ponderNextMove = newState;
15836 }
15837
15838 void
15839 NewSettingEvent (int option, int *feature, char *command, int value)
15840 {
15841     char buf[MSG_SIZ];
15842
15843     if (gameMode == EditPosition) EditPositionDone(TRUE);
15844     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15845     if(feature == NULL || *feature) SendToProgram(buf, &first);
15846     if (gameMode == TwoMachinesPlay) {
15847         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15848     }
15849 }
15850
15851 void
15852 ShowThinkingEvent ()
15853 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15854 {
15855     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15856     int newState = appData.showThinking
15857         // [HGM] thinking: other features now need thinking output as well
15858         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15859
15860     if (oldState == newState) return;
15861     oldState = newState;
15862     if (gameMode == EditPosition) EditPositionDone(TRUE);
15863     if (oldState) {
15864         SendToProgram("post\n", &first);
15865         if (gameMode == TwoMachinesPlay) {
15866             SendToProgram("post\n", &second);
15867         }
15868     } else {
15869         SendToProgram("nopost\n", &first);
15870         thinkOutput[0] = NULLCHAR;
15871         if (gameMode == TwoMachinesPlay) {
15872             SendToProgram("nopost\n", &second);
15873         }
15874     }
15875 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15876 }
15877
15878 void
15879 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15880 {
15881   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15882   if (pr == NoProc) return;
15883   AskQuestion(title, question, replyPrefix, pr);
15884 }
15885
15886 void
15887 TypeInEvent (char firstChar)
15888 {
15889     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15890         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15891         gameMode == AnalyzeMode || gameMode == EditGame || 
15892         gameMode == EditPosition || gameMode == IcsExamining ||
15893         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15894         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15895                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15896                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15897         gameMode == Training) PopUpMoveDialog(firstChar);
15898 }
15899
15900 void
15901 TypeInDoneEvent (char *move)
15902 {
15903         Board board;
15904         int n, fromX, fromY, toX, toY;
15905         char promoChar;
15906         ChessMove moveType;
15907
15908         // [HGM] FENedit
15909         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15910                 EditPositionPasteFEN(move);
15911                 return;
15912         }
15913         // [HGM] movenum: allow move number to be typed in any mode
15914         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15915           ToNrEvent(2*n-1);
15916           return;
15917         }
15918         // undocumented kludge: allow command-line option to be typed in!
15919         // (potentially fatal, and does not implement the effect of the option.)
15920         // should only be used for options that are values on which future decisions will be made,
15921         // and definitely not on options that would be used during initialization.
15922         if(strstr(move, "!!! -") == move) {
15923             ParseArgsFromString(move+4);
15924             return;
15925         }
15926
15927       if (gameMode != EditGame && currentMove != forwardMostMove && 
15928         gameMode != Training) {
15929         DisplayMoveError(_("Displayed move is not current"));
15930       } else {
15931         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15932           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15933         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15934         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15935           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15936           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15937         } else {
15938           DisplayMoveError(_("Could not parse move"));
15939         }
15940       }
15941 }
15942
15943 void
15944 DisplayMove (int moveNumber)
15945 {
15946     char message[MSG_SIZ];
15947     char res[MSG_SIZ];
15948     char cpThinkOutput[MSG_SIZ];
15949
15950     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15951
15952     if (moveNumber == forwardMostMove - 1 ||
15953         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15954
15955         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15956
15957         if (strchr(cpThinkOutput, '\n')) {
15958             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15959         }
15960     } else {
15961         *cpThinkOutput = NULLCHAR;
15962     }
15963
15964     /* [AS] Hide thinking from human user */
15965     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15966         *cpThinkOutput = NULLCHAR;
15967         if( thinkOutput[0] != NULLCHAR ) {
15968             int i;
15969
15970             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15971                 cpThinkOutput[i] = '.';
15972             }
15973             cpThinkOutput[i] = NULLCHAR;
15974             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15975         }
15976     }
15977
15978     if (moveNumber == forwardMostMove - 1 &&
15979         gameInfo.resultDetails != NULL) {
15980         if (gameInfo.resultDetails[0] == NULLCHAR) {
15981           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15982         } else {
15983           snprintf(res, MSG_SIZ, " {%s} %s",
15984                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15985         }
15986     } else {
15987         res[0] = NULLCHAR;
15988     }
15989
15990     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15991         DisplayMessage(res, cpThinkOutput);
15992     } else {
15993       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15994                 WhiteOnMove(moveNumber) ? " " : ".. ",
15995                 parseList[moveNumber], res);
15996         DisplayMessage(message, cpThinkOutput);
15997     }
15998 }
15999
16000 void
16001 DisplayComment (int moveNumber, char *text)
16002 {
16003     char title[MSG_SIZ];
16004
16005     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16006       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16007     } else {
16008       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16009               WhiteOnMove(moveNumber) ? " " : ".. ",
16010               parseList[moveNumber]);
16011     }
16012     if (text != NULL && (appData.autoDisplayComment || commentUp))
16013         CommentPopUp(title, text);
16014 }
16015
16016 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16017  * might be busy thinking or pondering.  It can be omitted if your
16018  * gnuchess is configured to stop thinking immediately on any user
16019  * input.  However, that gnuchess feature depends on the FIONREAD
16020  * ioctl, which does not work properly on some flavors of Unix.
16021  */
16022 void
16023 Attention (ChessProgramState *cps)
16024 {
16025 #if ATTENTION
16026     if (!cps->useSigint) return;
16027     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16028     switch (gameMode) {
16029       case MachinePlaysWhite:
16030       case MachinePlaysBlack:
16031       case TwoMachinesPlay:
16032       case IcsPlayingWhite:
16033       case IcsPlayingBlack:
16034       case AnalyzeMode:
16035       case AnalyzeFile:
16036         /* Skip if we know it isn't thinking */
16037         if (!cps->maybeThinking) return;
16038         if (appData.debugMode)
16039           fprintf(debugFP, "Interrupting %s\n", cps->which);
16040         InterruptChildProcess(cps->pr);
16041         cps->maybeThinking = FALSE;
16042         break;
16043       default:
16044         break;
16045     }
16046 #endif /*ATTENTION*/
16047 }
16048
16049 int
16050 CheckFlags ()
16051 {
16052     if (whiteTimeRemaining <= 0) {
16053         if (!whiteFlag) {
16054             whiteFlag = TRUE;
16055             if (appData.icsActive) {
16056                 if (appData.autoCallFlag &&
16057                     gameMode == IcsPlayingBlack && !blackFlag) {
16058                   SendToICS(ics_prefix);
16059                   SendToICS("flag\n");
16060                 }
16061             } else {
16062                 if (blackFlag) {
16063                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16064                 } else {
16065                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16066                     if (appData.autoCallFlag) {
16067                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16068                         return TRUE;
16069                     }
16070                 }
16071             }
16072         }
16073     }
16074     if (blackTimeRemaining <= 0) {
16075         if (!blackFlag) {
16076             blackFlag = TRUE;
16077             if (appData.icsActive) {
16078                 if (appData.autoCallFlag &&
16079                     gameMode == IcsPlayingWhite && !whiteFlag) {
16080                   SendToICS(ics_prefix);
16081                   SendToICS("flag\n");
16082                 }
16083             } else {
16084                 if (whiteFlag) {
16085                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16086                 } else {
16087                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16088                     if (appData.autoCallFlag) {
16089                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16090                         return TRUE;
16091                     }
16092                 }
16093             }
16094         }
16095     }
16096     return FALSE;
16097 }
16098
16099 void
16100 CheckTimeControl ()
16101 {
16102     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16103         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16104
16105     /*
16106      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16107      */
16108     if ( !WhiteOnMove(forwardMostMove) ) {
16109         /* White made time control */
16110         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16111         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16112         /* [HGM] time odds: correct new time quota for time odds! */
16113                                             / WhitePlayer()->timeOdds;
16114         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16115     } else {
16116         lastBlack -= blackTimeRemaining;
16117         /* Black made time control */
16118         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16119                                             / WhitePlayer()->other->timeOdds;
16120         lastWhite = whiteTimeRemaining;
16121     }
16122 }
16123
16124 void
16125 DisplayBothClocks ()
16126 {
16127     int wom = gameMode == EditPosition ?
16128       !blackPlaysFirst : WhiteOnMove(currentMove);
16129     DisplayWhiteClock(whiteTimeRemaining, wom);
16130     DisplayBlackClock(blackTimeRemaining, !wom);
16131 }
16132
16133
16134 /* Timekeeping seems to be a portability nightmare.  I think everyone
16135    has ftime(), but I'm really not sure, so I'm including some ifdefs
16136    to use other calls if you don't.  Clocks will be less accurate if
16137    you have neither ftime nor gettimeofday.
16138 */
16139
16140 /* VS 2008 requires the #include outside of the function */
16141 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16142 #include <sys/timeb.h>
16143 #endif
16144
16145 /* Get the current time as a TimeMark */
16146 void
16147 GetTimeMark (TimeMark *tm)
16148 {
16149 #if HAVE_GETTIMEOFDAY
16150
16151     struct timeval timeVal;
16152     struct timezone timeZone;
16153
16154     gettimeofday(&timeVal, &timeZone);
16155     tm->sec = (long) timeVal.tv_sec;
16156     tm->ms = (int) (timeVal.tv_usec / 1000L);
16157
16158 #else /*!HAVE_GETTIMEOFDAY*/
16159 #if HAVE_FTIME
16160
16161 // include <sys/timeb.h> / moved to just above start of function
16162     struct timeb timeB;
16163
16164     ftime(&timeB);
16165     tm->sec = (long) timeB.time;
16166     tm->ms = (int) timeB.millitm;
16167
16168 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16169     tm->sec = (long) time(NULL);
16170     tm->ms = 0;
16171 #endif
16172 #endif
16173 }
16174
16175 /* Return the difference in milliseconds between two
16176    time marks.  We assume the difference will fit in a long!
16177 */
16178 long
16179 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16180 {
16181     return 1000L*(tm2->sec - tm1->sec) +
16182            (long) (tm2->ms - tm1->ms);
16183 }
16184
16185
16186 /*
16187  * Code to manage the game clocks.
16188  *
16189  * In tournament play, black starts the clock and then white makes a move.
16190  * We give the human user a slight advantage if he is playing white---the
16191  * clocks don't run until he makes his first move, so it takes zero time.
16192  * Also, we don't account for network lag, so we could get out of sync
16193  * with GNU Chess's clock -- but then, referees are always right.
16194  */
16195
16196 static TimeMark tickStartTM;
16197 static long intendedTickLength;
16198
16199 long
16200 NextTickLength (long timeRemaining)
16201 {
16202     long nominalTickLength, nextTickLength;
16203
16204     if (timeRemaining > 0L && timeRemaining <= 10000L)
16205       nominalTickLength = 100L;
16206     else
16207       nominalTickLength = 1000L;
16208     nextTickLength = timeRemaining % nominalTickLength;
16209     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16210
16211     return nextTickLength;
16212 }
16213
16214 /* Adjust clock one minute up or down */
16215 void
16216 AdjustClock (Boolean which, int dir)
16217 {
16218     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16219     if(which) blackTimeRemaining += 60000*dir;
16220     else      whiteTimeRemaining += 60000*dir;
16221     DisplayBothClocks();
16222     adjustedClock = TRUE;
16223 }
16224
16225 /* Stop clocks and reset to a fresh time control */
16226 void
16227 ResetClocks ()
16228 {
16229     (void) StopClockTimer();
16230     if (appData.icsActive) {
16231         whiteTimeRemaining = blackTimeRemaining = 0;
16232     } else if (searchTime) {
16233         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16234         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16235     } else { /* [HGM] correct new time quote for time odds */
16236         whiteTC = blackTC = fullTimeControlString;
16237         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16238         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16239     }
16240     if (whiteFlag || blackFlag) {
16241         DisplayTitle("");
16242         whiteFlag = blackFlag = FALSE;
16243     }
16244     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16245     DisplayBothClocks();
16246     adjustedClock = FALSE;
16247 }
16248
16249 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16250
16251 /* Decrement running clock by amount of time that has passed */
16252 void
16253 DecrementClocks ()
16254 {
16255     long timeRemaining;
16256     long lastTickLength, fudge;
16257     TimeMark now;
16258
16259     if (!appData.clockMode) return;
16260     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16261
16262     GetTimeMark(&now);
16263
16264     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16265
16266     /* Fudge if we woke up a little too soon */
16267     fudge = intendedTickLength - lastTickLength;
16268     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16269
16270     if (WhiteOnMove(forwardMostMove)) {
16271         if(whiteNPS >= 0) lastTickLength = 0;
16272         timeRemaining = whiteTimeRemaining -= lastTickLength;
16273         if(timeRemaining < 0 && !appData.icsActive) {
16274             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16275             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16276                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16277                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16278             }
16279         }
16280         DisplayWhiteClock(whiteTimeRemaining - fudge,
16281                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16282     } else {
16283         if(blackNPS >= 0) lastTickLength = 0;
16284         timeRemaining = blackTimeRemaining -= lastTickLength;
16285         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16286             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16287             if(suddenDeath) {
16288                 blackStartMove = forwardMostMove;
16289                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16290             }
16291         }
16292         DisplayBlackClock(blackTimeRemaining - fudge,
16293                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16294     }
16295     if (CheckFlags()) return;
16296
16297     if(twoBoards) { // count down secondary board's clocks as well
16298         activePartnerTime -= lastTickLength;
16299         partnerUp = 1;
16300         if(activePartner == 'W')
16301             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16302         else
16303             DisplayBlackClock(activePartnerTime, TRUE);
16304         partnerUp = 0;
16305     }
16306
16307     tickStartTM = now;
16308     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16309     StartClockTimer(intendedTickLength);
16310
16311     /* if the time remaining has fallen below the alarm threshold, sound the
16312      * alarm. if the alarm has sounded and (due to a takeback or time control
16313      * with increment) the time remaining has increased to a level above the
16314      * threshold, reset the alarm so it can sound again.
16315      */
16316
16317     if (appData.icsActive && appData.icsAlarm) {
16318
16319         /* make sure we are dealing with the user's clock */
16320         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16321                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16322            )) return;
16323
16324         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16325             alarmSounded = FALSE;
16326         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16327             PlayAlarmSound();
16328             alarmSounded = TRUE;
16329         }
16330     }
16331 }
16332
16333
16334 /* A player has just moved, so stop the previously running
16335    clock and (if in clock mode) start the other one.
16336    We redisplay both clocks in case we're in ICS mode, because
16337    ICS gives us an update to both clocks after every move.
16338    Note that this routine is called *after* forwardMostMove
16339    is updated, so the last fractional tick must be subtracted
16340    from the color that is *not* on move now.
16341 */
16342 void
16343 SwitchClocks (int newMoveNr)
16344 {
16345     long lastTickLength;
16346     TimeMark now;
16347     int flagged = FALSE;
16348
16349     GetTimeMark(&now);
16350
16351     if (StopClockTimer() && appData.clockMode) {
16352         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16353         if (!WhiteOnMove(forwardMostMove)) {
16354             if(blackNPS >= 0) lastTickLength = 0;
16355             blackTimeRemaining -= lastTickLength;
16356            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16357 //         if(pvInfoList[forwardMostMove].time == -1)
16358                  pvInfoList[forwardMostMove].time =               // use GUI time
16359                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16360         } else {
16361            if(whiteNPS >= 0) lastTickLength = 0;
16362            whiteTimeRemaining -= lastTickLength;
16363            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16364 //         if(pvInfoList[forwardMostMove].time == -1)
16365                  pvInfoList[forwardMostMove].time =
16366                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16367         }
16368         flagged = CheckFlags();
16369     }
16370     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16371     CheckTimeControl();
16372
16373     if (flagged || !appData.clockMode) return;
16374
16375     switch (gameMode) {
16376       case MachinePlaysBlack:
16377       case MachinePlaysWhite:
16378       case BeginningOfGame:
16379         if (pausing) return;
16380         break;
16381
16382       case EditGame:
16383       case PlayFromGameFile:
16384       case IcsExamining:
16385         return;
16386
16387       default:
16388         break;
16389     }
16390
16391     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16392         if(WhiteOnMove(forwardMostMove))
16393              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16394         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16395     }
16396
16397     tickStartTM = now;
16398     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16399       whiteTimeRemaining : blackTimeRemaining);
16400     StartClockTimer(intendedTickLength);
16401 }
16402
16403
16404 /* Stop both clocks */
16405 void
16406 StopClocks ()
16407 {
16408     long lastTickLength;
16409     TimeMark now;
16410
16411     if (!StopClockTimer()) return;
16412     if (!appData.clockMode) return;
16413
16414     GetTimeMark(&now);
16415
16416     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16417     if (WhiteOnMove(forwardMostMove)) {
16418         if(whiteNPS >= 0) lastTickLength = 0;
16419         whiteTimeRemaining -= lastTickLength;
16420         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16421     } else {
16422         if(blackNPS >= 0) lastTickLength = 0;
16423         blackTimeRemaining -= lastTickLength;
16424         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16425     }
16426     CheckFlags();
16427 }
16428
16429 /* Start clock of player on move.  Time may have been reset, so
16430    if clock is already running, stop and restart it. */
16431 void
16432 StartClocks ()
16433 {
16434     (void) StopClockTimer(); /* in case it was running already */
16435     DisplayBothClocks();
16436     if (CheckFlags()) return;
16437
16438     if (!appData.clockMode) return;
16439     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16440
16441     GetTimeMark(&tickStartTM);
16442     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16443       whiteTimeRemaining : blackTimeRemaining);
16444
16445    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16446     whiteNPS = blackNPS = -1;
16447     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16448        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16449         whiteNPS = first.nps;
16450     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16451        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16452         blackNPS = first.nps;
16453     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16454         whiteNPS = second.nps;
16455     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16456         blackNPS = second.nps;
16457     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16458
16459     StartClockTimer(intendedTickLength);
16460 }
16461
16462 char *
16463 TimeString (long ms)
16464 {
16465     long second, minute, hour, day;
16466     char *sign = "";
16467     static char buf[32];
16468
16469     if (ms > 0 && ms <= 9900) {
16470       /* convert milliseconds to tenths, rounding up */
16471       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16472
16473       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16474       return buf;
16475     }
16476
16477     /* convert milliseconds to seconds, rounding up */
16478     /* use floating point to avoid strangeness of integer division
16479        with negative dividends on many machines */
16480     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16481
16482     if (second < 0) {
16483         sign = "-";
16484         second = -second;
16485     }
16486
16487     day = second / (60 * 60 * 24);
16488     second = second % (60 * 60 * 24);
16489     hour = second / (60 * 60);
16490     second = second % (60 * 60);
16491     minute = second / 60;
16492     second = second % 60;
16493
16494     if (day > 0)
16495       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16496               sign, day, hour, minute, second);
16497     else if (hour > 0)
16498       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16499     else
16500       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16501
16502     return buf;
16503 }
16504
16505
16506 /*
16507  * This is necessary because some C libraries aren't ANSI C compliant yet.
16508  */
16509 char *
16510 StrStr (char *string, char *match)
16511 {
16512     int i, length;
16513
16514     length = strlen(match);
16515
16516     for (i = strlen(string) - length; i >= 0; i--, string++)
16517       if (!strncmp(match, string, length))
16518         return string;
16519
16520     return NULL;
16521 }
16522
16523 char *
16524 StrCaseStr (char *string, char *match)
16525 {
16526     int i, j, length;
16527
16528     length = strlen(match);
16529
16530     for (i = strlen(string) - length; i >= 0; i--, string++) {
16531         for (j = 0; j < length; j++) {
16532             if (ToLower(match[j]) != ToLower(string[j]))
16533               break;
16534         }
16535         if (j == length) return string;
16536     }
16537
16538     return NULL;
16539 }
16540
16541 #ifndef _amigados
16542 int
16543 StrCaseCmp (char *s1, char *s2)
16544 {
16545     char c1, c2;
16546
16547     for (;;) {
16548         c1 = ToLower(*s1++);
16549         c2 = ToLower(*s2++);
16550         if (c1 > c2) return 1;
16551         if (c1 < c2) return -1;
16552         if (c1 == NULLCHAR) return 0;
16553     }
16554 }
16555
16556
16557 int
16558 ToLower (int c)
16559 {
16560     return isupper(c) ? tolower(c) : c;
16561 }
16562
16563
16564 int
16565 ToUpper (int c)
16566 {
16567     return islower(c) ? toupper(c) : c;
16568 }
16569 #endif /* !_amigados    */
16570
16571 char *
16572 StrSave (char *s)
16573 {
16574   char *ret;
16575
16576   if ((ret = (char *) malloc(strlen(s) + 1)))
16577     {
16578       safeStrCpy(ret, s, strlen(s)+1);
16579     }
16580   return ret;
16581 }
16582
16583 char *
16584 StrSavePtr (char *s, char **savePtr)
16585 {
16586     if (*savePtr) {
16587         free(*savePtr);
16588     }
16589     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16590       safeStrCpy(*savePtr, s, strlen(s)+1);
16591     }
16592     return(*savePtr);
16593 }
16594
16595 char *
16596 PGNDate ()
16597 {
16598     time_t clock;
16599     struct tm *tm;
16600     char buf[MSG_SIZ];
16601
16602     clock = time((time_t *)NULL);
16603     tm = localtime(&clock);
16604     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16605             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16606     return StrSave(buf);
16607 }
16608
16609
16610 char *
16611 PositionToFEN (int move, char *overrideCastling)
16612 {
16613     int i, j, fromX, fromY, toX, toY;
16614     int whiteToPlay;
16615     char buf[MSG_SIZ];
16616     char *p, *q;
16617     int emptycount;
16618     ChessSquare piece;
16619
16620     whiteToPlay = (gameMode == EditPosition) ?
16621       !blackPlaysFirst : (move % 2 == 0);
16622     p = buf;
16623
16624     /* Piece placement data */
16625     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16626         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16627         emptycount = 0;
16628         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16629             if (boards[move][i][j] == EmptySquare) {
16630                 emptycount++;
16631             } else { ChessSquare piece = boards[move][i][j];
16632                 if (emptycount > 0) {
16633                     if(emptycount<10) /* [HGM] can be >= 10 */
16634                         *p++ = '0' + emptycount;
16635                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16636                     emptycount = 0;
16637                 }
16638                 if(PieceToChar(piece) == '+') {
16639                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16640                     *p++ = '+';
16641                     piece = (ChessSquare)(DEMOTED piece);
16642                 }
16643                 *p++ = PieceToChar(piece);
16644                 if(p[-1] == '~') {
16645                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16646                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16647                     *p++ = '~';
16648                 }
16649             }
16650         }
16651         if (emptycount > 0) {
16652             if(emptycount<10) /* [HGM] can be >= 10 */
16653                 *p++ = '0' + emptycount;
16654             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16655             emptycount = 0;
16656         }
16657         *p++ = '/';
16658     }
16659     *(p - 1) = ' ';
16660
16661     /* [HGM] print Crazyhouse or Shogi holdings */
16662     if( gameInfo.holdingsWidth ) {
16663         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16664         q = p;
16665         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16666             piece = boards[move][i][BOARD_WIDTH-1];
16667             if( piece != EmptySquare )
16668               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16669                   *p++ = PieceToChar(piece);
16670         }
16671         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16672             piece = boards[move][BOARD_HEIGHT-i-1][0];
16673             if( piece != EmptySquare )
16674               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16675                   *p++ = PieceToChar(piece);
16676         }
16677
16678         if( q == p ) *p++ = '-';
16679         *p++ = ']';
16680         *p++ = ' ';
16681     }
16682
16683     /* Active color */
16684     *p++ = whiteToPlay ? 'w' : 'b';
16685     *p++ = ' ';
16686
16687   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16688     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16689   } else {
16690   if(nrCastlingRights) {
16691      q = p;
16692      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16693        /* [HGM] write directly from rights */
16694            if(boards[move][CASTLING][2] != NoRights &&
16695               boards[move][CASTLING][0] != NoRights   )
16696                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16697            if(boards[move][CASTLING][2] != NoRights &&
16698               boards[move][CASTLING][1] != NoRights   )
16699                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16700            if(boards[move][CASTLING][5] != NoRights &&
16701               boards[move][CASTLING][3] != NoRights   )
16702                 *p++ = boards[move][CASTLING][3] + AAA;
16703            if(boards[move][CASTLING][5] != NoRights &&
16704               boards[move][CASTLING][4] != NoRights   )
16705                 *p++ = boards[move][CASTLING][4] + AAA;
16706      } else {
16707
16708         /* [HGM] write true castling rights */
16709         if( nrCastlingRights == 6 ) {
16710             int q, k=0;
16711             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16712                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
16713             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
16714                  boards[move][CASTLING][2] != NoRights  );
16715             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
16716                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
16717                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16718                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
16719                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
16720             }
16721             if(q) *p++ = 'Q';
16722             k = 0;
16723             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16724                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
16725             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
16726                  boards[move][CASTLING][5] != NoRights  );
16727             if(gameInfo.variant == VariantSChess) {
16728                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
16729                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16730                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
16731                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
16732             }
16733             if(q) *p++ = 'q';
16734         }
16735      }
16736      if (q == p) *p++ = '-'; /* No castling rights */
16737      *p++ = ' ';
16738   }
16739
16740   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16741      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16742     /* En passant target square */
16743     if (move > backwardMostMove) {
16744         fromX = moveList[move - 1][0] - AAA;
16745         fromY = moveList[move - 1][1] - ONE;
16746         toX = moveList[move - 1][2] - AAA;
16747         toY = moveList[move - 1][3] - ONE;
16748         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16749             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16750             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16751             fromX == toX) {
16752             /* 2-square pawn move just happened */
16753             *p++ = toX + AAA;
16754             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16755         } else {
16756             *p++ = '-';
16757         }
16758     } else if(move == backwardMostMove) {
16759         // [HGM] perhaps we should always do it like this, and forget the above?
16760         if((signed char)boards[move][EP_STATUS] >= 0) {
16761             *p++ = boards[move][EP_STATUS] + AAA;
16762             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16763         } else {
16764             *p++ = '-';
16765         }
16766     } else {
16767         *p++ = '-';
16768     }
16769     *p++ = ' ';
16770   }
16771   }
16772
16773     /* [HGM] find reversible plies */
16774     {   int i = 0, j=move;
16775
16776         if (appData.debugMode) { int k;
16777             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16778             for(k=backwardMostMove; k<=forwardMostMove; k++)
16779                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16780
16781         }
16782
16783         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16784         if( j == backwardMostMove ) i += initialRulePlies;
16785         sprintf(p, "%d ", i);
16786         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16787     }
16788     /* Fullmove number */
16789     sprintf(p, "%d", (move / 2) + 1);
16790
16791     return StrSave(buf);
16792 }
16793
16794 Boolean
16795 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16796 {
16797     int i, j;
16798     char *p, c;
16799     int emptycount, virgin[BOARD_FILES];
16800     ChessSquare piece;
16801
16802     p = fen;
16803
16804     /* [HGM] by default clear Crazyhouse holdings, if present */
16805     if(gameInfo.holdingsWidth) {
16806        for(i=0; i<BOARD_HEIGHT; i++) {
16807            board[i][0]             = EmptySquare; /* black holdings */
16808            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16809            board[i][1]             = (ChessSquare) 0; /* black counts */
16810            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16811        }
16812     }
16813
16814     /* Piece placement data */
16815     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16816         j = 0;
16817         for (;;) {
16818             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16819                 if (*p == '/') p++;
16820                 emptycount = gameInfo.boardWidth - j;
16821                 while (emptycount--)
16822                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16823                 break;
16824 #if(BOARD_FILES >= 10)
16825             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16826                 p++; emptycount=10;
16827                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16828                 while (emptycount--)
16829                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16830 #endif
16831             } else if (isdigit(*p)) {
16832                 emptycount = *p++ - '0';
16833                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16834                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16835                 while (emptycount--)
16836                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16837             } else if (*p == '+' || isalpha(*p)) {
16838                 if (j >= gameInfo.boardWidth) return FALSE;
16839                 if(*p=='+') {
16840                     piece = CharToPiece(*++p);
16841                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16842                     piece = (ChessSquare) (PROMOTED piece ); p++;
16843                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16844                 } else piece = CharToPiece(*p++);
16845
16846                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16847                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16848                     piece = (ChessSquare) (PROMOTED piece);
16849                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16850                     p++;
16851                 }
16852                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16853             } else {
16854                 return FALSE;
16855             }
16856         }
16857     }
16858     while (*p == '/' || *p == ' ') p++;
16859
16860     /* [HGM] look for Crazyhouse holdings here */
16861     while(*p==' ') p++;
16862     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16863         if(*p == '[') p++;
16864         if(*p == '-' ) p++; /* empty holdings */ else {
16865             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16866             /* if we would allow FEN reading to set board size, we would   */
16867             /* have to add holdings and shift the board read so far here   */
16868             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16869                 p++;
16870                 if((int) piece >= (int) BlackPawn ) {
16871                     i = (int)piece - (int)BlackPawn;
16872                     i = PieceToNumber((ChessSquare)i);
16873                     if( i >= gameInfo.holdingsSize ) return FALSE;
16874                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16875                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16876                 } else {
16877                     i = (int)piece - (int)WhitePawn;
16878                     i = PieceToNumber((ChessSquare)i);
16879                     if( i >= gameInfo.holdingsSize ) return FALSE;
16880                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16881                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16882                 }
16883             }
16884         }
16885         if(*p == ']') p++;
16886     }
16887
16888     while(*p == ' ') p++;
16889
16890     /* Active color */
16891     c = *p++;
16892     if(appData.colorNickNames) {
16893       if( c == appData.colorNickNames[0] ) c = 'w'; else
16894       if( c == appData.colorNickNames[1] ) c = 'b';
16895     }
16896     switch (c) {
16897       case 'w':
16898         *blackPlaysFirst = FALSE;
16899         break;
16900       case 'b':
16901         *blackPlaysFirst = TRUE;
16902         break;
16903       default:
16904         return FALSE;
16905     }
16906
16907     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16908     /* return the extra info in global variiables             */
16909
16910     /* set defaults in case FEN is incomplete */
16911     board[EP_STATUS] = EP_UNKNOWN;
16912     for(i=0; i<nrCastlingRights; i++ ) {
16913         board[CASTLING][i] =
16914             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16915     }   /* assume possible unless obviously impossible */
16916     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16917     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16918     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16919                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16920     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16921     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16922     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16923                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16924     FENrulePlies = 0;
16925
16926     while(*p==' ') p++;
16927     if(nrCastlingRights) {
16928       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
16929       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
16930           /* castling indicator present, so default becomes no castlings */
16931           for(i=0; i<nrCastlingRights; i++ ) {
16932                  board[CASTLING][i] = NoRights;
16933           }
16934       }
16935       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16936              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
16937              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16938              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16939         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
16940
16941         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16942             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16943             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16944         }
16945         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16946             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16947         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16948                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16949         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16950                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16951         switch(c) {
16952           case'K':
16953               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16954               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16955               board[CASTLING][2] = whiteKingFile;
16956               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
16957               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
16958               break;
16959           case'Q':
16960               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16961               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16962               board[CASTLING][2] = whiteKingFile;
16963               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
16964               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
16965               break;
16966           case'k':
16967               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16968               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16969               board[CASTLING][5] = blackKingFile;
16970               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
16971               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
16972               break;
16973           case'q':
16974               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16975               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16976               board[CASTLING][5] = blackKingFile;
16977               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
16978               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
16979           case '-':
16980               break;
16981           default: /* FRC castlings */
16982               if(c >= 'a') { /* black rights */
16983                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
16984                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16985                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16986                   if(i == BOARD_RGHT) break;
16987                   board[CASTLING][5] = i;
16988                   c -= AAA;
16989                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16990                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16991                   if(c > i)
16992                       board[CASTLING][3] = c;
16993                   else
16994                       board[CASTLING][4] = c;
16995               } else { /* white rights */
16996                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
16997                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16998                     if(board[0][i] == WhiteKing) break;
16999                   if(i == BOARD_RGHT) break;
17000                   board[CASTLING][2] = i;
17001                   c -= AAA - 'a' + 'A';
17002                   if(board[0][c] >= WhiteKing) break;
17003                   if(c > i)
17004                       board[CASTLING][0] = c;
17005                   else
17006                       board[CASTLING][1] = c;
17007               }
17008         }
17009       }
17010       for(i=0; i<nrCastlingRights; i++)
17011         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17012       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17013     if (appData.debugMode) {
17014         fprintf(debugFP, "FEN castling rights:");
17015         for(i=0; i<nrCastlingRights; i++)
17016         fprintf(debugFP, " %d", board[CASTLING][i]);
17017         fprintf(debugFP, "\n");
17018     }
17019
17020       while(*p==' ') p++;
17021     }
17022
17023     /* read e.p. field in games that know e.p. capture */
17024     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17025        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17026       if(*p=='-') {
17027         p++; board[EP_STATUS] = EP_NONE;
17028       } else {
17029          char c = *p++ - AAA;
17030
17031          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17032          if(*p >= '0' && *p <='9') p++;
17033          board[EP_STATUS] = c;
17034       }
17035     }
17036
17037
17038     if(sscanf(p, "%d", &i) == 1) {
17039         FENrulePlies = i; /* 50-move ply counter */
17040         /* (The move number is still ignored)    */
17041     }
17042
17043     return TRUE;
17044 }
17045
17046 void
17047 EditPositionPasteFEN (char *fen)
17048 {
17049   if (fen != NULL) {
17050     Board initial_position;
17051
17052     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17053       DisplayError(_("Bad FEN position in clipboard"), 0);
17054       return ;
17055     } else {
17056       int savedBlackPlaysFirst = blackPlaysFirst;
17057       EditPositionEvent();
17058       blackPlaysFirst = savedBlackPlaysFirst;
17059       CopyBoard(boards[0], initial_position);
17060       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17061       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17062       DisplayBothClocks();
17063       DrawPosition(FALSE, boards[currentMove]);
17064     }
17065   }
17066 }
17067
17068 static char cseq[12] = "\\   ";
17069
17070 Boolean
17071 set_cont_sequence (char *new_seq)
17072 {
17073     int len;
17074     Boolean ret;
17075
17076     // handle bad attempts to set the sequence
17077         if (!new_seq)
17078                 return 0; // acceptable error - no debug
17079
17080     len = strlen(new_seq);
17081     ret = (len > 0) && (len < sizeof(cseq));
17082     if (ret)
17083       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17084     else if (appData.debugMode)
17085       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17086     return ret;
17087 }
17088
17089 /*
17090     reformat a source message so words don't cross the width boundary.  internal
17091     newlines are not removed.  returns the wrapped size (no null character unless
17092     included in source message).  If dest is NULL, only calculate the size required
17093     for the dest buffer.  lp argument indicats line position upon entry, and it's
17094     passed back upon exit.
17095 */
17096 int
17097 wrap (char *dest, char *src, int count, int width, int *lp)
17098 {
17099     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17100
17101     cseq_len = strlen(cseq);
17102     old_line = line = *lp;
17103     ansi = len = clen = 0;
17104
17105     for (i=0; i < count; i++)
17106     {
17107         if (src[i] == '\033')
17108             ansi = 1;
17109
17110         // if we hit the width, back up
17111         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17112         {
17113             // store i & len in case the word is too long
17114             old_i = i, old_len = len;
17115
17116             // find the end of the last word
17117             while (i && src[i] != ' ' && src[i] != '\n')
17118             {
17119                 i--;
17120                 len--;
17121             }
17122
17123             // word too long?  restore i & len before splitting it
17124             if ((old_i-i+clen) >= width)
17125             {
17126                 i = old_i;
17127                 len = old_len;
17128             }
17129
17130             // extra space?
17131             if (i && src[i-1] == ' ')
17132                 len--;
17133
17134             if (src[i] != ' ' && src[i] != '\n')
17135             {
17136                 i--;
17137                 if (len)
17138                     len--;
17139             }
17140
17141             // now append the newline and continuation sequence
17142             if (dest)
17143                 dest[len] = '\n';
17144             len++;
17145             if (dest)
17146                 strncpy(dest+len, cseq, cseq_len);
17147             len += cseq_len;
17148             line = cseq_len;
17149             clen = cseq_len;
17150             continue;
17151         }
17152
17153         if (dest)
17154             dest[len] = src[i];
17155         len++;
17156         if (!ansi)
17157             line++;
17158         if (src[i] == '\n')
17159             line = 0;
17160         if (src[i] == 'm')
17161             ansi = 0;
17162     }
17163     if (dest && appData.debugMode)
17164     {
17165         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17166             count, width, line, len, *lp);
17167         show_bytes(debugFP, src, count);
17168         fprintf(debugFP, "\ndest: ");
17169         show_bytes(debugFP, dest, len);
17170         fprintf(debugFP, "\n");
17171     }
17172     *lp = dest ? line : old_line;
17173
17174     return len;
17175 }
17176
17177 // [HGM] vari: routines for shelving variations
17178 Boolean modeRestore = FALSE;
17179
17180 void
17181 PushInner (int firstMove, int lastMove)
17182 {
17183         int i, j, nrMoves = lastMove - firstMove;
17184
17185         // push current tail of game on stack
17186         savedResult[storedGames] = gameInfo.result;
17187         savedDetails[storedGames] = gameInfo.resultDetails;
17188         gameInfo.resultDetails = NULL;
17189         savedFirst[storedGames] = firstMove;
17190         savedLast [storedGames] = lastMove;
17191         savedFramePtr[storedGames] = framePtr;
17192         framePtr -= nrMoves; // reserve space for the boards
17193         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17194             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17195             for(j=0; j<MOVE_LEN; j++)
17196                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17197             for(j=0; j<2*MOVE_LEN; j++)
17198                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17199             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17200             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17201             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17202             pvInfoList[firstMove+i-1].depth = 0;
17203             commentList[framePtr+i] = commentList[firstMove+i];
17204             commentList[firstMove+i] = NULL;
17205         }
17206
17207         storedGames++;
17208         forwardMostMove = firstMove; // truncate game so we can start variation
17209 }
17210
17211 void
17212 PushTail (int firstMove, int lastMove)
17213 {
17214         if(appData.icsActive) { // only in local mode
17215                 forwardMostMove = currentMove; // mimic old ICS behavior
17216                 return;
17217         }
17218         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17219
17220         PushInner(firstMove, lastMove);
17221         if(storedGames == 1) GreyRevert(FALSE);
17222         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17223 }
17224
17225 void
17226 PopInner (Boolean annotate)
17227 {
17228         int i, j, nrMoves;
17229         char buf[8000], moveBuf[20];
17230
17231         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17232         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17233         nrMoves = savedLast[storedGames] - currentMove;
17234         if(annotate) {
17235                 int cnt = 10;
17236                 if(!WhiteOnMove(currentMove))
17237                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17238                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17239                 for(i=currentMove; i<forwardMostMove; i++) {
17240                         if(WhiteOnMove(i))
17241                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17242                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17243                         strcat(buf, moveBuf);
17244                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17245                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17246                 }
17247                 strcat(buf, ")");
17248         }
17249         for(i=1; i<=nrMoves; i++) { // copy last variation back
17250             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17251             for(j=0; j<MOVE_LEN; j++)
17252                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17253             for(j=0; j<2*MOVE_LEN; j++)
17254                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17255             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17256             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17257             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17258             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17259             commentList[currentMove+i] = commentList[framePtr+i];
17260             commentList[framePtr+i] = NULL;
17261         }
17262         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17263         framePtr = savedFramePtr[storedGames];
17264         gameInfo.result = savedResult[storedGames];
17265         if(gameInfo.resultDetails != NULL) {
17266             free(gameInfo.resultDetails);
17267       }
17268         gameInfo.resultDetails = savedDetails[storedGames];
17269         forwardMostMove = currentMove + nrMoves;
17270 }
17271
17272 Boolean
17273 PopTail (Boolean annotate)
17274 {
17275         if(appData.icsActive) return FALSE; // only in local mode
17276         if(!storedGames) return FALSE; // sanity
17277         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17278
17279         PopInner(annotate);
17280         if(currentMove < forwardMostMove) ForwardEvent(); else
17281         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17282
17283         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17284         return TRUE;
17285 }
17286
17287 void
17288 CleanupTail ()
17289 {       // remove all shelved variations
17290         int i;
17291         for(i=0; i<storedGames; i++) {
17292             if(savedDetails[i])
17293                 free(savedDetails[i]);
17294             savedDetails[i] = NULL;
17295         }
17296         for(i=framePtr; i<MAX_MOVES; i++) {
17297                 if(commentList[i]) free(commentList[i]);
17298                 commentList[i] = NULL;
17299         }
17300         framePtr = MAX_MOVES-1;
17301         storedGames = 0;
17302 }
17303
17304 void
17305 LoadVariation (int index, char *text)
17306 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17307         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17308         int level = 0, move;
17309
17310         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17311         // first find outermost bracketing variation
17312         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17313             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17314                 if(*p == '{') wait = '}'; else
17315                 if(*p == '[') wait = ']'; else
17316                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17317                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17318             }
17319             if(*p == wait) wait = NULLCHAR; // closing ]} found
17320             p++;
17321         }
17322         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17323         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17324         end[1] = NULLCHAR; // clip off comment beyond variation
17325         ToNrEvent(currentMove-1);
17326         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17327         // kludge: use ParsePV() to append variation to game
17328         move = currentMove;
17329         ParsePV(start, TRUE, TRUE);
17330         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17331         ClearPremoveHighlights();
17332         CommentPopDown();
17333         ToNrEvent(currentMove+1);
17334 }
17335