f733800ab28842b4340fa6a731c6e4671add5ae1
[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 ics_printf P((char *format, ...));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226
227 #ifdef WIN32
228        extern void ConsoleCreate();
229 #endif
230
231 ChessProgramState *WhitePlayer();
232 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
233 int VerifyDisplayMode P(());
234
235 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
236 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
237 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
238 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
239 void ics_update_width P((int new_width));
240 extern char installDir[MSG_SIZ];
241 VariantClass startVariant; /* [HGM] nicks: initial variant */
242 Boolean abortMatch;
243
244 extern int tinyLayout, smallLayout;
245 ChessProgramStats programStats;
246 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
247 int endPV = -1;
248 static int exiting = 0; /* [HGM] moved to top */
249 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
250 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
251 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
252 int partnerHighlight[2];
253 Boolean partnerBoardValid = 0;
254 char partnerStatus[MSG_SIZ];
255 Boolean partnerUp;
256 Boolean originalFlip;
257 Boolean twoBoards = 0;
258 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
259 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
260 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
261 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
262 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
263 int opponentKibitzes;
264 int lastSavedGame; /* [HGM] save: ID of game */
265 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
266 extern int chatCount;
267 int chattingPartner;
268 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
269 char lastMsg[MSG_SIZ];
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
273
274 /* States for ics_getting_history */
275 #define H_FALSE 0
276 #define H_REQUESTED 1
277 #define H_GOT_REQ_HEADER 2
278 #define H_GOT_UNREQ_HEADER 3
279 #define H_GETTING_MOVES 4
280 #define H_GOT_UNWANTED_HEADER 5
281
282 /* whosays values for GameEnds */
283 #define GE_ICS 0
284 #define GE_ENGINE 1
285 #define GE_PLAYER 2
286 #define GE_FILE 3
287 #define GE_XBOARD 4
288 #define GE_ENGINE1 5
289 #define GE_ENGINE2 6
290
291 /* Maximum number of games in a cmail message */
292 #define CMAIL_MAX_GAMES 20
293
294 /* Different types of move when calling RegisterMove */
295 #define CMAIL_MOVE   0
296 #define CMAIL_RESIGN 1
297 #define CMAIL_DRAW   2
298 #define CMAIL_ACCEPT 3
299
300 /* Different types of result to remember for each game */
301 #define CMAIL_NOT_RESULT 0
302 #define CMAIL_OLD_RESULT 1
303 #define CMAIL_NEW_RESULT 2
304
305 /* Telnet protocol constants */
306 #define TN_WILL 0373
307 #define TN_WONT 0374
308 #define TN_DO   0375
309 #define TN_DONT 0376
310 #define TN_IAC  0377
311 #define TN_ECHO 0001
312 #define TN_SGA  0003
313 #define TN_PORT 23
314
315 char*
316 safeStrCpy (char *dst, const char *src, size_t count)
317 { // [HGM] made safe
318   int i;
319   assert( dst != NULL );
320   assert( src != NULL );
321   assert( count > 0 );
322
323   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
324   if(  i == count && dst[count-1] != NULLCHAR)
325     {
326       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
327       if(appData.debugMode)
328       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
329     }
330
331   return dst;
332 }
333
334 /* Some compiler can't cast u64 to double
335  * This function do the job for us:
336
337  * We use the highest bit for cast, this only
338  * works if the highest bit is not
339  * in use (This should not happen)
340  *
341  * We used this for all compiler
342  */
343 double
344 u64ToDouble (u64 value)
345 {
346   double r;
347   u64 tmp = value & u64Const(0x7fffffffffffffff);
348   r = (double)(s64)tmp;
349   if (value & u64Const(0x8000000000000000))
350        r +=  9.2233720368547758080e18; /* 2^63 */
351  return r;
352 }
353
354 /* Fake up flags for now, as we aren't keeping track of castling
355    availability yet. [HGM] Change of logic: the flag now only
356    indicates the type of castlings allowed by the rule of the game.
357    The actual rights themselves are maintained in the array
358    castlingRights, as part of the game history, and are not probed
359    by this function.
360  */
361 int
362 PosFlags (index)
363 {
364   int flags = F_ALL_CASTLE_OK;
365   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
366   switch (gameInfo.variant) {
367   case VariantSuicide:
368     flags &= ~F_ALL_CASTLE_OK;
369   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
370     flags |= F_IGNORE_CHECK;
371   case VariantLosers:
372     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
373     break;
374   case VariantAtomic:
375     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
376     break;
377   case VariantKriegspiel:
378     flags |= F_KRIEGSPIEL_CAPTURE;
379     break;
380   case VariantCapaRandom:
381   case VariantFischeRandom:
382     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
383   case VariantNoCastle:
384   case VariantShatranj:
385   case VariantCourier:
386   case VariantMakruk:
387   case VariantGrand:
388     flags &= ~F_ALL_CASTLE_OK;
389     break;
390   default:
391     break;
392   }
393   return flags;
394 }
395
396 FILE *gameFileFP, *debugFP, *serverFP;
397 char *currentDebugFile; // [HGM] debug split: to remember name
398
399 /*
400     [AS] Note: sometimes, the sscanf() function is used to parse the input
401     into a fixed-size buffer. Because of this, we must be prepared to
402     receive strings as long as the size of the input buffer, which is currently
403     set to 4K for Windows and 8K for the rest.
404     So, we must either allocate sufficiently large buffers here, or
405     reduce the size of the input buffer in the input reading part.
406 */
407
408 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
409 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
410 char thinkOutput1[MSG_SIZ*10];
411
412 ChessProgramState first, second, pairing;
413
414 /* premove variables */
415 int premoveToX = 0;
416 int premoveToY = 0;
417 int premoveFromX = 0;
418 int premoveFromY = 0;
419 int premovePromoChar = 0;
420 int gotPremove = 0;
421 Boolean alarmSounded;
422 /* end premove variables */
423
424 char *ics_prefix = "$";
425 int ics_type = ICS_GENERIC;
426
427 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
428 int pauseExamForwardMostMove = 0;
429 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
430 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
431 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
432 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
433 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
434 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
435 int whiteFlag = FALSE, blackFlag = FALSE;
436 int userOfferedDraw = FALSE;
437 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
438 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
439 int cmailMoveType[CMAIL_MAX_GAMES];
440 long ics_clock_paused = 0;
441 ProcRef icsPR = NoProc, cmailPR = NoProc;
442 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
443 GameMode gameMode = BeginningOfGame;
444 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
445 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
446 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
447 int hiddenThinkOutputState = 0; /* [AS] */
448 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
449 int adjudicateLossPlies = 6;
450 char white_holding[64], black_holding[64];
451 TimeMark lastNodeCountTime;
452 long lastNodeCount=0;
453 int shiftKey; // [HGM] set by mouse handler
454
455 int have_sent_ICS_logon = 0;
456 int movesPerSession;
457 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
458 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
459 Boolean adjustedClock;
460 long timeControl_2; /* [AS] Allow separate time controls */
461 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
462 long timeRemaining[2][MAX_MOVES];
463 int matchGame = 0, nextGame = 0, roundNr = 0;
464 Boolean waitingForGame = FALSE;
465 TimeMark programStartTime, pauseStart;
466 char ics_handle[MSG_SIZ];
467 int have_set_title = 0;
468
469 /* animateTraining preserves the state of appData.animate
470  * when Training mode is activated. This allows the
471  * response to be animated when appData.animate == TRUE and
472  * appData.animateDragging == TRUE.
473  */
474 Boolean animateTraining;
475
476 GameInfo gameInfo;
477
478 AppData appData;
479
480 Board boards[MAX_MOVES];
481 /* [HGM] Following 7 needed for accurate legality tests: */
482 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
483 signed char  initialRights[BOARD_FILES];
484 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
485 int   initialRulePlies, FENrulePlies;
486 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
487 int loadFlag = 0;
488 Boolean shuffleOpenings;
489 int mute; // mute all sounds
490
491 // [HGM] vari: next 12 to save and restore variations
492 #define MAX_VARIATIONS 10
493 int framePtr = MAX_MOVES-1; // points to free stack entry
494 int storedGames = 0;
495 int savedFirst[MAX_VARIATIONS];
496 int savedLast[MAX_VARIATIONS];
497 int savedFramePtr[MAX_VARIATIONS];
498 char *savedDetails[MAX_VARIATIONS];
499 ChessMove savedResult[MAX_VARIATIONS];
500
501 void PushTail P((int firstMove, int lastMove));
502 Boolean PopTail P((Boolean annotate));
503 void PushInner P((int firstMove, int lastMove));
504 void PopInner P((Boolean annotate));
505 void CleanupTail P((void));
506
507 ChessSquare  FIDEArray[2][BOARD_FILES] = {
508     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
509         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
510     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
511         BlackKing, BlackBishop, BlackKnight, BlackRook }
512 };
513
514 ChessSquare twoKingsArray[2][BOARD_FILES] = {
515     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518         BlackKing, BlackKing, BlackKnight, BlackRook }
519 };
520
521 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
522     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
523         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
524     { BlackRook, BlackMan, BlackBishop, BlackQueen,
525         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
526 };
527
528 ChessSquare SpartanArray[2][BOARD_FILES] = {
529     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
530         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
531     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
532         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
533 };
534
535 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
536     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
539         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
540 };
541
542 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
543     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
544         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
545     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
546         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
547 };
548
549 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
551         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackMan, BlackFerz,
553         BlackKing, BlackMan, BlackKnight, BlackRook }
554 };
555
556
557 #if (BOARD_FILES>=10)
558 ChessSquare ShogiArray[2][BOARD_FILES] = {
559     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
560         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
561     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
562         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
563 };
564
565 ChessSquare XiangqiArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
567         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
568     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
569         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
570 };
571
572 ChessSquare CapablancaArray[2][BOARD_FILES] = {
573     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
574         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
575     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
576         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
577 };
578
579 ChessSquare GreatArray[2][BOARD_FILES] = {
580     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
581         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
582     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
583         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
584 };
585
586 ChessSquare JanusArray[2][BOARD_FILES] = {
587     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
588         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
589     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
590         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
591 };
592
593 ChessSquare GrandArray[2][BOARD_FILES] = {
594     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
595         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
596     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
597         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
598 };
599
600 #ifdef GOTHIC
601 ChessSquare GothicArray[2][BOARD_FILES] = {
602     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
603         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
604     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
605         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
606 };
607 #else // !GOTHIC
608 #define GothicArray CapablancaArray
609 #endif // !GOTHIC
610
611 #ifdef FALCON
612 ChessSquare FalconArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
614         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
616         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
617 };
618 #else // !FALCON
619 #define FalconArray CapablancaArray
620 #endif // !FALCON
621
622 #else // !(BOARD_FILES>=10)
623 #define XiangqiPosition FIDEArray
624 #define CapablancaArray FIDEArray
625 #define GothicArray FIDEArray
626 #define GreatArray FIDEArray
627 #endif // !(BOARD_FILES>=10)
628
629 #if (BOARD_FILES>=12)
630 ChessSquare CourierArray[2][BOARD_FILES] = {
631     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
632         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
633     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
634         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
635 };
636 #else // !(BOARD_FILES>=12)
637 #define CourierArray CapablancaArray
638 #endif // !(BOARD_FILES>=12)
639
640
641 Board initialPosition;
642
643
644 /* Convert str to a rating. Checks for special cases of "----",
645
646    "++++", etc. Also strips ()'s */
647 int
648 string_to_rating (char *str)
649 {
650   while(*str && !isdigit(*str)) ++str;
651   if (!*str)
652     return 0;   /* One of the special "no rating" cases */
653   else
654     return atoi(str);
655 }
656
657 void
658 ClearProgramStats ()
659 {
660     /* Init programStats */
661     programStats.movelist[0] = 0;
662     programStats.depth = 0;
663     programStats.nr_moves = 0;
664     programStats.moves_left = 0;
665     programStats.nodes = 0;
666     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
667     programStats.score = 0;
668     programStats.got_only_move = 0;
669     programStats.got_fail = 0;
670     programStats.line_is_book = 0;
671 }
672
673 void
674 CommonEngineInit ()
675 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
676     if (appData.firstPlaysBlack) {
677         first.twoMachinesColor = "black\n";
678         second.twoMachinesColor = "white\n";
679     } else {
680         first.twoMachinesColor = "white\n";
681         second.twoMachinesColor = "black\n";
682     }
683
684     first.other = &second;
685     second.other = &first;
686
687     { float norm = 1;
688         if(appData.timeOddsMode) {
689             norm = appData.timeOdds[0];
690             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
691         }
692         first.timeOdds  = appData.timeOdds[0]/norm;
693         second.timeOdds = appData.timeOdds[1]/norm;
694     }
695
696     if(programVersion) free(programVersion);
697     if (appData.noChessProgram) {
698         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
699         sprintf(programVersion, "%s", PACKAGE_STRING);
700     } else {
701       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
702       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
703       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
704     }
705 }
706
707 void
708 UnloadEngine (ChessProgramState *cps)
709 {
710         /* Kill off first chess program */
711         if (cps->isr != NULL)
712           RemoveInputSource(cps->isr);
713         cps->isr = NULL;
714
715         if (cps->pr != NoProc) {
716             ExitAnalyzeMode();
717             DoSleep( appData.delayBeforeQuit );
718             SendToProgram("quit\n", cps);
719             DoSleep( appData.delayAfterQuit );
720             DestroyChildProcess(cps->pr, cps->useSigterm);
721         }
722         cps->pr = NoProc;
723         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
724 }
725
726 void
727 ClearOptions (ChessProgramState *cps)
728 {
729     int i;
730     cps->nrOptions = cps->comboCnt = 0;
731     for(i=0; i<MAX_OPTIONS; i++) {
732         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
733         cps->option[i].textValue = 0;
734     }
735 }
736
737 char *engineNames[] = {
738   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
739      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
740 N_("first"),
741   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
742      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
743 N_("second")
744 };
745
746 void
747 InitEngine (ChessProgramState *cps, int n)
748 {   // [HGM] all engine initialiation put in a function that does one engine
749
750     ClearOptions(cps);
751
752     cps->which = engineNames[n];
753     cps->maybeThinking = FALSE;
754     cps->pr = NoProc;
755     cps->isr = NULL;
756     cps->sendTime = 2;
757     cps->sendDrawOffers = 1;
758
759     cps->program = appData.chessProgram[n];
760     cps->host = appData.host[n];
761     cps->dir = appData.directory[n];
762     cps->initString = appData.engInitString[n];
763     cps->computerString = appData.computerString[n];
764     cps->useSigint  = TRUE;
765     cps->useSigterm = TRUE;
766     cps->reuse = appData.reuse[n];
767     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
768     cps->useSetboard = FALSE;
769     cps->useSAN = FALSE;
770     cps->usePing = FALSE;
771     cps->lastPing = 0;
772     cps->lastPong = 0;
773     cps->usePlayother = FALSE;
774     cps->useColors = TRUE;
775     cps->useUsermove = FALSE;
776     cps->sendICS = FALSE;
777     cps->sendName = appData.icsActive;
778     cps->sdKludge = FALSE;
779     cps->stKludge = FALSE;
780     TidyProgramName(cps->program, cps->host, cps->tidy);
781     cps->matchWins = 0;
782     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
783     cps->analysisSupport = 2; /* detect */
784     cps->analyzing = FALSE;
785     cps->initDone = FALSE;
786
787     /* New features added by Tord: */
788     cps->useFEN960 = FALSE;
789     cps->useOOCastle = TRUE;
790     /* End of new features added by Tord. */
791     cps->fenOverride  = appData.fenOverride[n];
792
793     /* [HGM] time odds: set factor for each machine */
794     cps->timeOdds  = appData.timeOdds[n];
795
796     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
797     cps->accumulateTC = appData.accumulateTC[n];
798     cps->maxNrOfSessions = 1;
799
800     /* [HGM] debug */
801     cps->debug = FALSE;
802
803     cps->supportsNPS = UNKNOWN;
804     cps->memSize = FALSE;
805     cps->maxCores = FALSE;
806     cps->egtFormats[0] = NULLCHAR;
807
808     /* [HGM] options */
809     cps->optionSettings  = appData.engOptions[n];
810
811     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
812     cps->isUCI = appData.isUCI[n]; /* [AS] */
813     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
814
815     if (appData.protocolVersion[n] > PROTOVER
816         || appData.protocolVersion[n] < 1)
817       {
818         char buf[MSG_SIZ];
819         int len;
820
821         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
822                        appData.protocolVersion[n]);
823         if( (len >= MSG_SIZ) && appData.debugMode )
824           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
825
826         DisplayFatalError(buf, 0, 2);
827       }
828     else
829       {
830         cps->protocolVersion = appData.protocolVersion[n];
831       }
832
833     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
834     ParseFeatures(appData.featureDefaults, cps);
835 }
836
837 ChessProgramState *savCps;
838
839 void
840 LoadEngine ()
841 {
842     int i;
843     if(WaitForEngine(savCps, LoadEngine)) return;
844     CommonEngineInit(); // recalculate time odds
845     if(gameInfo.variant != StringToVariant(appData.variant)) {
846         // we changed variant when loading the engine; this forces us to reset
847         Reset(TRUE, savCps != &first);
848         EditGameEvent(); // for consistency with other path, as Reset changes mode
849     }
850     InitChessProgram(savCps, FALSE);
851     SendToProgram("force\n", savCps);
852     DisplayMessage("", "");
853     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
854     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
855     ThawUI();
856     SetGNUMode();
857 }
858
859 void
860 ReplaceEngine (ChessProgramState *cps, int n)
861 {
862     EditGameEvent();
863     UnloadEngine(cps);
864     appData.noChessProgram = FALSE;
865     appData.clockMode = TRUE;
866     InitEngine(cps, n);
867     UpdateLogos(TRUE);
868     if(n) return; // only startup first engine immediately; second can wait
869     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
870     LoadEngine();
871 }
872
873 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
874 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
875
876 static char resetOptions[] = 
877         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
878         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
879         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
880         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
881
882 void
883 FloatToFront(char **list, char *engineLine)
884 {
885     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
886     int i=0;
887     if(appData.recentEngines <= 0) return;
888     TidyProgramName(engineLine, "localhost", tidy+1);
889     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
890     strncpy(buf+1, *list, MSG_SIZ-50);
891     if(p = strstr(buf, tidy)) { // tidy name appears in list
892         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
893         while(*p++ = *++q); // squeeze out
894     }
895     strcat(tidy, buf+1); // put list behind tidy name
896     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
897     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
898     ASSIGN(*list, tidy+1);
899 }
900
901 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
902
903 void
904 Load (ChessProgramState *cps, int i)
905 {
906     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
907     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
908         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
909         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
910         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
911         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
912         appData.firstProtocolVersion = PROTOVER;
913         ParseArgsFromString(buf);
914         SwapEngines(i);
915         ReplaceEngine(cps, i);
916         FloatToFront(&appData.recentEngineList, engineLine);
917         return;
918     }
919     p = engineName;
920     while(q = strchr(p, SLASH)) p = q+1;
921     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
922     if(engineDir[0] != NULLCHAR) {
923         ASSIGN(appData.directory[i], engineDir);
924     } else if(p != engineName) { // derive directory from engine path, when not given
925         p[-1] = 0;
926         ASSIGN(appData.directory[i], engineName);
927         p[-1] = SLASH;
928         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
929     } else { ASSIGN(appData.directory[i], "."); }
930     if(params[0]) {
931         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
932         snprintf(command, MSG_SIZ, "%s %s", p, params);
933         p = command;
934     }
935     ASSIGN(appData.chessProgram[i], p);
936     appData.isUCI[i] = isUCI;
937     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
938     appData.hasOwnBookUCI[i] = hasBook;
939     if(!nickName[0]) useNick = FALSE;
940     if(useNick) ASSIGN(appData.pgnName[i], nickName);
941     if(addToList) {
942         int len;
943         char quote;
944         q = firstChessProgramNames;
945         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
946         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
947         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
948                         quote, p, quote, appData.directory[i], 
949                         useNick ? " -fn \"" : "",
950                         useNick ? nickName : "",
951                         useNick ? "\"" : "",
952                         v1 ? " -firstProtocolVersion 1" : "",
953                         hasBook ? "" : " -fNoOwnBookUCI",
954                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
955                         storeVariant ? " -variant " : "",
956                         storeVariant ? VariantName(gameInfo.variant) : "");
957         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
958         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
959         if(insert != q) insert[-1] = NULLCHAR;
960         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
961         if(q)   free(q);
962         FloatToFront(&appData.recentEngineList, buf);
963     }
964     ReplaceEngine(cps, i);
965 }
966
967 void
968 InitTimeControls ()
969 {
970     int matched, min, sec;
971     /*
972      * Parse timeControl resource
973      */
974     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
975                           appData.movesPerSession)) {
976         char buf[MSG_SIZ];
977         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
978         DisplayFatalError(buf, 0, 2);
979     }
980
981     /*
982      * Parse searchTime resource
983      */
984     if (*appData.searchTime != NULLCHAR) {
985         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
986         if (matched == 1) {
987             searchTime = min * 60;
988         } else if (matched == 2) {
989             searchTime = min * 60 + sec;
990         } else {
991             char buf[MSG_SIZ];
992             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
993             DisplayFatalError(buf, 0, 2);
994         }
995     }
996 }
997
998 void
999 InitBackEnd1 ()
1000 {
1001
1002     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1003     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1004
1005     GetTimeMark(&programStartTime);
1006     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1007     appData.seedBase = random() + (random()<<15);
1008     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1009
1010     ClearProgramStats();
1011     programStats.ok_to_send = 1;
1012     programStats.seen_stat = 0;
1013
1014     /*
1015      * Initialize game list
1016      */
1017     ListNew(&gameList);
1018
1019
1020     /*
1021      * Internet chess server status
1022      */
1023     if (appData.icsActive) {
1024         appData.matchMode = FALSE;
1025         appData.matchGames = 0;
1026 #if ZIPPY
1027         appData.noChessProgram = !appData.zippyPlay;
1028 #else
1029         appData.zippyPlay = FALSE;
1030         appData.zippyTalk = FALSE;
1031         appData.noChessProgram = TRUE;
1032 #endif
1033         if (*appData.icsHelper != NULLCHAR) {
1034             appData.useTelnet = TRUE;
1035             appData.telnetProgram = appData.icsHelper;
1036         }
1037     } else {
1038         appData.zippyTalk = appData.zippyPlay = FALSE;
1039     }
1040
1041     /* [AS] Initialize pv info list [HGM] and game state */
1042     {
1043         int i, j;
1044
1045         for( i=0; i<=framePtr; i++ ) {
1046             pvInfoList[i].depth = -1;
1047             boards[i][EP_STATUS] = EP_NONE;
1048             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1049         }
1050     }
1051
1052     InitTimeControls();
1053
1054     /* [AS] Adjudication threshold */
1055     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1056
1057     InitEngine(&first, 0);
1058     InitEngine(&second, 1);
1059     CommonEngineInit();
1060
1061     pairing.which = "pairing"; // pairing engine
1062     pairing.pr = NoProc;
1063     pairing.isr = NULL;
1064     pairing.program = appData.pairingEngine;
1065     pairing.host = "localhost";
1066     pairing.dir = ".";
1067
1068     if (appData.icsActive) {
1069         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1070     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1071         appData.clockMode = FALSE;
1072         first.sendTime = second.sendTime = 0;
1073     }
1074
1075 #if ZIPPY
1076     /* Override some settings from environment variables, for backward
1077        compatibility.  Unfortunately it's not feasible to have the env
1078        vars just set defaults, at least in xboard.  Ugh.
1079     */
1080     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1081       ZippyInit();
1082     }
1083 #endif
1084
1085     if (!appData.icsActive) {
1086       char buf[MSG_SIZ];
1087       int len;
1088
1089       /* Check for variants that are supported only in ICS mode,
1090          or not at all.  Some that are accepted here nevertheless
1091          have bugs; see comments below.
1092       */
1093       VariantClass variant = StringToVariant(appData.variant);
1094       switch (variant) {
1095       case VariantBughouse:     /* need four players and two boards */
1096       case VariantKriegspiel:   /* need to hide pieces and move details */
1097         /* case VariantFischeRandom: (Fabien: moved below) */
1098         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1099         if( (len >= MSG_SIZ) && appData.debugMode )
1100           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1101
1102         DisplayFatalError(buf, 0, 2);
1103         return;
1104
1105       case VariantUnknown:
1106       case VariantLoadable:
1107       case Variant29:
1108       case Variant30:
1109       case Variant31:
1110       case Variant32:
1111       case Variant33:
1112       case Variant34:
1113       case Variant35:
1114       case Variant36:
1115       default:
1116         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1117         if( (len >= MSG_SIZ) && appData.debugMode )
1118           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1119
1120         DisplayFatalError(buf, 0, 2);
1121         return;
1122
1123       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1124       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1125       case VariantGothic:     /* [HGM] should work */
1126       case VariantCapablanca: /* [HGM] should work */
1127       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1128       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1129       case VariantKnightmate: /* [HGM] should work */
1130       case VariantCylinder:   /* [HGM] untested */
1131       case VariantFalcon:     /* [HGM] untested */
1132       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1133                                  offboard interposition not understood */
1134       case VariantNormal:     /* definitely works! */
1135       case VariantWildCastle: /* pieces not automatically shuffled */
1136       case VariantNoCastle:   /* pieces not automatically shuffled */
1137       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1138       case VariantLosers:     /* should work except for win condition,
1139                                  and doesn't know captures are mandatory */
1140       case VariantSuicide:    /* should work except for win condition,
1141                                  and doesn't know captures are mandatory */
1142       case VariantGiveaway:   /* should work except for win condition,
1143                                  and doesn't know captures are mandatory */
1144       case VariantTwoKings:   /* should work */
1145       case VariantAtomic:     /* should work except for win condition */
1146       case Variant3Check:     /* should work except for win condition */
1147       case VariantShatranj:   /* should work except for all win conditions */
1148       case VariantMakruk:     /* should work except for draw countdown */
1149       case VariantBerolina:   /* might work if TestLegality is off */
1150       case VariantCapaRandom: /* should work */
1151       case VariantJanus:      /* should work */
1152       case VariantSuper:      /* experimental */
1153       case VariantGreat:      /* experimental, requires legality testing to be off */
1154       case VariantSChess:     /* S-Chess, should work */
1155       case VariantGrand:      /* should work */
1156       case VariantSpartan:    /* should work */
1157         break;
1158       }
1159     }
1160
1161 }
1162
1163 int
1164 NextIntegerFromString (char ** str, long * value)
1165 {
1166     int result = -1;
1167     char * s = *str;
1168
1169     while( *s == ' ' || *s == '\t' ) {
1170         s++;
1171     }
1172
1173     *value = 0;
1174
1175     if( *s >= '0' && *s <= '9' ) {
1176         while( *s >= '0' && *s <= '9' ) {
1177             *value = *value * 10 + (*s - '0');
1178             s++;
1179         }
1180
1181         result = 0;
1182     }
1183
1184     *str = s;
1185
1186     return result;
1187 }
1188
1189 int
1190 NextTimeControlFromString (char ** str, long * value)
1191 {
1192     long temp;
1193     int result = NextIntegerFromString( str, &temp );
1194
1195     if( result == 0 ) {
1196         *value = temp * 60; /* Minutes */
1197         if( **str == ':' ) {
1198             (*str)++;
1199             result = NextIntegerFromString( str, &temp );
1200             *value += temp; /* Seconds */
1201         }
1202     }
1203
1204     return result;
1205 }
1206
1207 int
1208 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1209 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1210     int result = -1, type = 0; long temp, temp2;
1211
1212     if(**str != ':') return -1; // old params remain in force!
1213     (*str)++;
1214     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1215     if( NextIntegerFromString( str, &temp ) ) return -1;
1216     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1217
1218     if(**str != '/') {
1219         /* time only: incremental or sudden-death time control */
1220         if(**str == '+') { /* increment follows; read it */
1221             (*str)++;
1222             if(**str == '!') type = *(*str)++; // Bronstein TC
1223             if(result = NextIntegerFromString( str, &temp2)) return -1;
1224             *inc = temp2 * 1000;
1225             if(**str == '.') { // read fraction of increment
1226                 char *start = ++(*str);
1227                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1228                 temp2 *= 1000;
1229                 while(start++ < *str) temp2 /= 10;
1230                 *inc += temp2;
1231             }
1232         } else *inc = 0;
1233         *moves = 0; *tc = temp * 1000; *incType = type;
1234         return 0;
1235     }
1236
1237     (*str)++; /* classical time control */
1238     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1239
1240     if(result == 0) {
1241         *moves = temp;
1242         *tc    = temp2 * 1000;
1243         *inc   = 0;
1244         *incType = type;
1245     }
1246     return result;
1247 }
1248
1249 int
1250 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1251 {   /* [HGM] get time to add from the multi-session time-control string */
1252     int incType, moves=1; /* kludge to force reading of first session */
1253     long time, increment;
1254     char *s = tcString;
1255
1256     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1257     do {
1258         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1259         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1260         if(movenr == -1) return time;    /* last move before new session     */
1261         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1262         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1263         if(!moves) return increment;     /* current session is incremental   */
1264         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1265     } while(movenr >= -1);               /* try again for next session       */
1266
1267     return 0; // no new time quota on this move
1268 }
1269
1270 int
1271 ParseTimeControl (char *tc, float ti, int mps)
1272 {
1273   long tc1;
1274   long tc2;
1275   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1276   int min, sec=0;
1277
1278   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1279   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1280       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1281   if(ti > 0) {
1282
1283     if(mps)
1284       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1285     else 
1286       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1287   } else {
1288     if(mps)
1289       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1290     else 
1291       snprintf(buf, MSG_SIZ, ":%s", mytc);
1292   }
1293   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1294   
1295   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1296     return FALSE;
1297   }
1298
1299   if( *tc == '/' ) {
1300     /* Parse second time control */
1301     tc++;
1302
1303     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1304       return FALSE;
1305     }
1306
1307     if( tc2 == 0 ) {
1308       return FALSE;
1309     }
1310
1311     timeControl_2 = tc2 * 1000;
1312   }
1313   else {
1314     timeControl_2 = 0;
1315   }
1316
1317   if( tc1 == 0 ) {
1318     return FALSE;
1319   }
1320
1321   timeControl = tc1 * 1000;
1322
1323   if (ti >= 0) {
1324     timeIncrement = ti * 1000;  /* convert to ms */
1325     movesPerSession = 0;
1326   } else {
1327     timeIncrement = 0;
1328     movesPerSession = mps;
1329   }
1330   return TRUE;
1331 }
1332
1333 void
1334 InitBackEnd2 ()
1335 {
1336     if (appData.debugMode) {
1337         fprintf(debugFP, "%s\n", programVersion);
1338     }
1339     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1340
1341     set_cont_sequence(appData.wrapContSeq);
1342     if (appData.matchGames > 0) {
1343         appData.matchMode = TRUE;
1344     } else if (appData.matchMode) {
1345         appData.matchGames = 1;
1346     }
1347     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1348         appData.matchGames = appData.sameColorGames;
1349     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1350         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1351         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1352     }
1353     Reset(TRUE, FALSE);
1354     if (appData.noChessProgram || first.protocolVersion == 1) {
1355       InitBackEnd3();
1356     } else {
1357       /* kludge: allow timeout for initial "feature" commands */
1358       FreezeUI();
1359       DisplayMessage("", _("Starting chess program"));
1360       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1361     }
1362 }
1363
1364 int
1365 CalculateIndex (int index, int gameNr)
1366 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1367     int res;
1368     if(index > 0) return index; // fixed nmber
1369     if(index == 0) return 1;
1370     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1371     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1372     return res;
1373 }
1374
1375 int
1376 LoadGameOrPosition (int gameNr)
1377 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1378     if (*appData.loadGameFile != NULLCHAR) {
1379         if (!LoadGameFromFile(appData.loadGameFile,
1380                 CalculateIndex(appData.loadGameIndex, gameNr),
1381                               appData.loadGameFile, FALSE)) {
1382             DisplayFatalError(_("Bad game file"), 0, 1);
1383             return 0;
1384         }
1385     } else if (*appData.loadPositionFile != NULLCHAR) {
1386         if (!LoadPositionFromFile(appData.loadPositionFile,
1387                 CalculateIndex(appData.loadPositionIndex, gameNr),
1388                                   appData.loadPositionFile)) {
1389             DisplayFatalError(_("Bad position file"), 0, 1);
1390             return 0;
1391         }
1392     }
1393     return 1;
1394 }
1395
1396 void
1397 ReserveGame (int gameNr, char resChar)
1398 {
1399     FILE *tf = fopen(appData.tourneyFile, "r+");
1400     char *p, *q, c, buf[MSG_SIZ];
1401     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1402     safeStrCpy(buf, lastMsg, MSG_SIZ);
1403     DisplayMessage(_("Pick new game"), "");
1404     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1405     ParseArgsFromFile(tf);
1406     p = q = appData.results;
1407     if(appData.debugMode) {
1408       char *r = appData.participants;
1409       fprintf(debugFP, "results = '%s'\n", p);
1410       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1411       fprintf(debugFP, "\n");
1412     }
1413     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1414     nextGame = q - p;
1415     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1416     safeStrCpy(q, p, strlen(p) + 2);
1417     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1418     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1419     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1420         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1421         q[nextGame] = '*';
1422     }
1423     fseek(tf, -(strlen(p)+4), SEEK_END);
1424     c = fgetc(tf);
1425     if(c != '"') // depending on DOS or Unix line endings we can be one off
1426          fseek(tf, -(strlen(p)+2), SEEK_END);
1427     else fseek(tf, -(strlen(p)+3), SEEK_END);
1428     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1429     DisplayMessage(buf, "");
1430     free(p); appData.results = q;
1431     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1432        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1433       int round = appData.defaultMatchGames * appData.tourneyType;
1434       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1435          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1436         UnloadEngine(&first);  // next game belongs to other pairing;
1437         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1438     }
1439     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1440 }
1441
1442 void
1443 MatchEvent (int mode)
1444 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1445         int dummy;
1446         if(matchMode) { // already in match mode: switch it off
1447             abortMatch = TRUE;
1448             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1449             return;
1450         }
1451 //      if(gameMode != BeginningOfGame) {
1452 //          DisplayError(_("You can only start a match from the initial position."), 0);
1453 //          return;
1454 //      }
1455         abortMatch = FALSE;
1456         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1457         /* Set up machine vs. machine match */
1458         nextGame = 0;
1459         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1460         if(appData.tourneyFile[0]) {
1461             ReserveGame(-1, 0);
1462             if(nextGame > appData.matchGames) {
1463                 char buf[MSG_SIZ];
1464                 if(strchr(appData.results, '*') == NULL) {
1465                     FILE *f;
1466                     appData.tourneyCycles++;
1467                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1468                         fclose(f);
1469                         NextTourneyGame(-1, &dummy);
1470                         ReserveGame(-1, 0);
1471                         if(nextGame <= appData.matchGames) {
1472                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1473                             matchMode = mode;
1474                             ScheduleDelayedEvent(NextMatchGame, 10000);
1475                             return;
1476                         }
1477                     }
1478                 }
1479                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1480                 DisplayError(buf, 0);
1481                 appData.tourneyFile[0] = 0;
1482                 return;
1483             }
1484         } else
1485         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1486             DisplayFatalError(_("Can't have a match with no chess programs"),
1487                               0, 2);
1488             return;
1489         }
1490         matchMode = mode;
1491         matchGame = roundNr = 1;
1492         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1493         NextMatchGame();
1494 }
1495
1496 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1497
1498 void
1499 InitBackEnd3 P((void))
1500 {
1501     GameMode initialMode;
1502     char buf[MSG_SIZ];
1503     int err, len;
1504
1505     InitChessProgram(&first, startedFromSetupPosition);
1506
1507     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1508         free(programVersion);
1509         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1510         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1511         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1512     }
1513
1514     if (appData.icsActive) {
1515 #ifdef WIN32
1516         /* [DM] Make a console window if needed [HGM] merged ifs */
1517         ConsoleCreate();
1518 #endif
1519         err = establish();
1520         if (err != 0)
1521           {
1522             if (*appData.icsCommPort != NULLCHAR)
1523               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1524                              appData.icsCommPort);
1525             else
1526               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1527                         appData.icsHost, appData.icsPort);
1528
1529             if( (len >= MSG_SIZ) && appData.debugMode )
1530               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1531
1532             DisplayFatalError(buf, err, 1);
1533             return;
1534         }
1535         SetICSMode();
1536         telnetISR =
1537           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1538         fromUserISR =
1539           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1540         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1541             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1542     } else if (appData.noChessProgram) {
1543         SetNCPMode();
1544     } else {
1545         SetGNUMode();
1546     }
1547
1548     if (*appData.cmailGameName != NULLCHAR) {
1549         SetCmailMode();
1550         OpenLoopback(&cmailPR);
1551         cmailISR =
1552           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1553     }
1554
1555     ThawUI();
1556     DisplayMessage("", "");
1557     if (StrCaseCmp(appData.initialMode, "") == 0) {
1558       initialMode = BeginningOfGame;
1559       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1560         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1561         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1562         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1563         ModeHighlight();
1564       }
1565     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1566       initialMode = TwoMachinesPlay;
1567     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1568       initialMode = AnalyzeFile;
1569     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1570       initialMode = AnalyzeMode;
1571     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1572       initialMode = MachinePlaysWhite;
1573     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1574       initialMode = MachinePlaysBlack;
1575     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1576       initialMode = EditGame;
1577     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1578       initialMode = EditPosition;
1579     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1580       initialMode = Training;
1581     } else {
1582       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1583       if( (len >= MSG_SIZ) && appData.debugMode )
1584         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1585
1586       DisplayFatalError(buf, 0, 2);
1587       return;
1588     }
1589
1590     if (appData.matchMode) {
1591         if(appData.tourneyFile[0]) { // start tourney from command line
1592             FILE *f;
1593             if(f = fopen(appData.tourneyFile, "r")) {
1594                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1595                 fclose(f);
1596                 appData.clockMode = TRUE;
1597                 SetGNUMode();
1598             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1599         }
1600         MatchEvent(TRUE);
1601     } else if (*appData.cmailGameName != NULLCHAR) {
1602         /* Set up cmail mode */
1603         ReloadCmailMsgEvent(TRUE);
1604     } else {
1605         /* Set up other modes */
1606         if (initialMode == AnalyzeFile) {
1607           if (*appData.loadGameFile == NULLCHAR) {
1608             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1609             return;
1610           }
1611         }
1612         if (*appData.loadGameFile != NULLCHAR) {
1613             (void) LoadGameFromFile(appData.loadGameFile,
1614                                     appData.loadGameIndex,
1615                                     appData.loadGameFile, TRUE);
1616         } else if (*appData.loadPositionFile != NULLCHAR) {
1617             (void) LoadPositionFromFile(appData.loadPositionFile,
1618                                         appData.loadPositionIndex,
1619                                         appData.loadPositionFile);
1620             /* [HGM] try to make self-starting even after FEN load */
1621             /* to allow automatic setup of fairy variants with wtm */
1622             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1623                 gameMode = BeginningOfGame;
1624                 setboardSpoiledMachineBlack = 1;
1625             }
1626             /* [HGM] loadPos: make that every new game uses the setup */
1627             /* from file as long as we do not switch variant          */
1628             if(!blackPlaysFirst) {
1629                 startedFromPositionFile = TRUE;
1630                 CopyBoard(filePosition, boards[0]);
1631             }
1632         }
1633         if (initialMode == AnalyzeMode) {
1634           if (appData.noChessProgram) {
1635             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1636             return;
1637           }
1638           if (appData.icsActive) {
1639             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1640             return;
1641           }
1642           AnalyzeModeEvent();
1643         } else if (initialMode == AnalyzeFile) {
1644           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1645           ShowThinkingEvent();
1646           AnalyzeFileEvent();
1647           AnalysisPeriodicEvent(1);
1648         } else if (initialMode == MachinePlaysWhite) {
1649           if (appData.noChessProgram) {
1650             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1651                               0, 2);
1652             return;
1653           }
1654           if (appData.icsActive) {
1655             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1656                               0, 2);
1657             return;
1658           }
1659           MachineWhiteEvent();
1660         } else if (initialMode == MachinePlaysBlack) {
1661           if (appData.noChessProgram) {
1662             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1663                               0, 2);
1664             return;
1665           }
1666           if (appData.icsActive) {
1667             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1668                               0, 2);
1669             return;
1670           }
1671           MachineBlackEvent();
1672         } else if (initialMode == TwoMachinesPlay) {
1673           if (appData.noChessProgram) {
1674             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1675                               0, 2);
1676             return;
1677           }
1678           if (appData.icsActive) {
1679             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1680                               0, 2);
1681             return;
1682           }
1683           TwoMachinesEvent();
1684         } else if (initialMode == EditGame) {
1685           EditGameEvent();
1686         } else if (initialMode == EditPosition) {
1687           EditPositionEvent();
1688         } else if (initialMode == Training) {
1689           if (*appData.loadGameFile == NULLCHAR) {
1690             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1691             return;
1692           }
1693           TrainingEvent();
1694         }
1695     }
1696 }
1697
1698 void
1699 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1700 {
1701     DisplayBook(current+1);
1702
1703     MoveHistorySet( movelist, first, last, current, pvInfoList );
1704
1705     EvalGraphSet( first, last, current, pvInfoList );
1706
1707     MakeEngineOutputTitle();
1708 }
1709
1710 /*
1711  * Establish will establish a contact to a remote host.port.
1712  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1713  *  used to talk to the host.
1714  * Returns 0 if okay, error code if not.
1715  */
1716 int
1717 establish ()
1718 {
1719     char buf[MSG_SIZ];
1720
1721     if (*appData.icsCommPort != NULLCHAR) {
1722         /* Talk to the host through a serial comm port */
1723         return OpenCommPort(appData.icsCommPort, &icsPR);
1724
1725     } else if (*appData.gateway != NULLCHAR) {
1726         if (*appData.remoteShell == NULLCHAR) {
1727             /* Use the rcmd protocol to run telnet program on a gateway host */
1728             snprintf(buf, sizeof(buf), "%s %s %s",
1729                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1730             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1731
1732         } else {
1733             /* Use the rsh program to run telnet program on a gateway host */
1734             if (*appData.remoteUser == NULLCHAR) {
1735                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1736                         appData.gateway, appData.telnetProgram,
1737                         appData.icsHost, appData.icsPort);
1738             } else {
1739                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1740                         appData.remoteShell, appData.gateway,
1741                         appData.remoteUser, appData.telnetProgram,
1742                         appData.icsHost, appData.icsPort);
1743             }
1744             return StartChildProcess(buf, "", &icsPR);
1745
1746         }
1747     } else if (appData.useTelnet) {
1748         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1749
1750     } else {
1751         /* TCP socket interface differs somewhat between
1752            Unix and NT; handle details in the front end.
1753            */
1754         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1755     }
1756 }
1757
1758 void
1759 EscapeExpand (char *p, char *q)
1760 {       // [HGM] initstring: routine to shape up string arguments
1761         while(*p++ = *q++) if(p[-1] == '\\')
1762             switch(*q++) {
1763                 case 'n': p[-1] = '\n'; break;
1764                 case 'r': p[-1] = '\r'; break;
1765                 case 't': p[-1] = '\t'; break;
1766                 case '\\': p[-1] = '\\'; break;
1767                 case 0: *p = 0; return;
1768                 default: p[-1] = q[-1]; break;
1769             }
1770 }
1771
1772 void
1773 show_bytes (FILE *fp, char *buf, int count)
1774 {
1775     while (count--) {
1776         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1777             fprintf(fp, "\\%03o", *buf & 0xff);
1778         } else {
1779             putc(*buf, fp);
1780         }
1781         buf++;
1782     }
1783     fflush(fp);
1784 }
1785
1786 /* Returns an errno value */
1787 int
1788 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1789 {
1790     char buf[8192], *p, *q, *buflim;
1791     int left, newcount, outcount;
1792
1793     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1794         *appData.gateway != NULLCHAR) {
1795         if (appData.debugMode) {
1796             fprintf(debugFP, ">ICS: ");
1797             show_bytes(debugFP, message, count);
1798             fprintf(debugFP, "\n");
1799         }
1800         return OutputToProcess(pr, message, count, outError);
1801     }
1802
1803     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1804     p = message;
1805     q = buf;
1806     left = count;
1807     newcount = 0;
1808     while (left) {
1809         if (q >= buflim) {
1810             if (appData.debugMode) {
1811                 fprintf(debugFP, ">ICS: ");
1812                 show_bytes(debugFP, buf, newcount);
1813                 fprintf(debugFP, "\n");
1814             }
1815             outcount = OutputToProcess(pr, buf, newcount, outError);
1816             if (outcount < newcount) return -1; /* to be sure */
1817             q = buf;
1818             newcount = 0;
1819         }
1820         if (*p == '\n') {
1821             *q++ = '\r';
1822             newcount++;
1823         } else if (((unsigned char) *p) == TN_IAC) {
1824             *q++ = (char) TN_IAC;
1825             newcount ++;
1826         }
1827         *q++ = *p++;
1828         newcount++;
1829         left--;
1830     }
1831     if (appData.debugMode) {
1832         fprintf(debugFP, ">ICS: ");
1833         show_bytes(debugFP, buf, newcount);
1834         fprintf(debugFP, "\n");
1835     }
1836     outcount = OutputToProcess(pr, buf, newcount, outError);
1837     if (outcount < newcount) return -1; /* to be sure */
1838     return count;
1839 }
1840
1841 void
1842 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1843 {
1844     int outError, outCount;
1845     static int gotEof = 0;
1846
1847     /* Pass data read from player on to ICS */
1848     if (count > 0) {
1849         gotEof = 0;
1850         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1851         if (outCount < count) {
1852             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1853         }
1854     } else if (count < 0) {
1855         RemoveInputSource(isr);
1856         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1857     } else if (gotEof++ > 0) {
1858         RemoveInputSource(isr);
1859         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1860     }
1861 }
1862
1863 void
1864 KeepAlive ()
1865 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1866     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1867     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1868     SendToICS("date\n");
1869     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1870 }
1871
1872 /* added routine for printf style output to ics */
1873 void
1874 ics_printf (char *format, ...)
1875 {
1876     char buffer[MSG_SIZ];
1877     va_list args;
1878
1879     va_start(args, format);
1880     vsnprintf(buffer, sizeof(buffer), format, args);
1881     buffer[sizeof(buffer)-1] = '\0';
1882     SendToICS(buffer);
1883     va_end(args);
1884 }
1885
1886 void
1887 SendToICS (char *s)
1888 {
1889     int count, outCount, outError;
1890
1891     if (icsPR == NoProc) return;
1892
1893     count = strlen(s);
1894     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1895     if (outCount < count) {
1896         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1897     }
1898 }
1899
1900 /* This is used for sending logon scripts to the ICS. Sending
1901    without a delay causes problems when using timestamp on ICC
1902    (at least on my machine). */
1903 void
1904 SendToICSDelayed (char *s, long msdelay)
1905 {
1906     int count, outCount, outError;
1907
1908     if (icsPR == NoProc) return;
1909
1910     count = strlen(s);
1911     if (appData.debugMode) {
1912         fprintf(debugFP, ">ICS: ");
1913         show_bytes(debugFP, s, count);
1914         fprintf(debugFP, "\n");
1915     }
1916     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1917                                       msdelay);
1918     if (outCount < count) {
1919         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1920     }
1921 }
1922
1923
1924 /* Remove all highlighting escape sequences in s
1925    Also deletes any suffix starting with '('
1926    */
1927 char *
1928 StripHighlightAndTitle (char *s)
1929 {
1930     static char retbuf[MSG_SIZ];
1931     char *p = retbuf;
1932
1933     while (*s != NULLCHAR) {
1934         while (*s == '\033') {
1935             while (*s != NULLCHAR && !isalpha(*s)) s++;
1936             if (*s != NULLCHAR) s++;
1937         }
1938         while (*s != NULLCHAR && *s != '\033') {
1939             if (*s == '(' || *s == '[') {
1940                 *p = NULLCHAR;
1941                 return retbuf;
1942             }
1943             *p++ = *s++;
1944         }
1945     }
1946     *p = NULLCHAR;
1947     return retbuf;
1948 }
1949
1950 /* Remove all highlighting escape sequences in s */
1951 char *
1952 StripHighlight (char *s)
1953 {
1954     static char retbuf[MSG_SIZ];
1955     char *p = retbuf;
1956
1957     while (*s != NULLCHAR) {
1958         while (*s == '\033') {
1959             while (*s != NULLCHAR && !isalpha(*s)) s++;
1960             if (*s != NULLCHAR) s++;
1961         }
1962         while (*s != NULLCHAR && *s != '\033') {
1963             *p++ = *s++;
1964         }
1965     }
1966     *p = NULLCHAR;
1967     return retbuf;
1968 }
1969
1970 char *variantNames[] = VARIANT_NAMES;
1971 char *
1972 VariantName (VariantClass v)
1973 {
1974     return variantNames[v];
1975 }
1976
1977
1978 /* Identify a variant from the strings the chess servers use or the
1979    PGN Variant tag names we use. */
1980 VariantClass
1981 StringToVariant (char *e)
1982 {
1983     char *p;
1984     int wnum = -1;
1985     VariantClass v = VariantNormal;
1986     int i, found = FALSE;
1987     char buf[MSG_SIZ];
1988     int len;
1989
1990     if (!e) return v;
1991
1992     /* [HGM] skip over optional board-size prefixes */
1993     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1994         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1995         while( *e++ != '_');
1996     }
1997
1998     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1999         v = VariantNormal;
2000         found = TRUE;
2001     } else
2002     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2003       if (StrCaseStr(e, variantNames[i])) {
2004         v = (VariantClass) i;
2005         found = TRUE;
2006         break;
2007       }
2008     }
2009
2010     if (!found) {
2011       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2012           || StrCaseStr(e, "wild/fr")
2013           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2014         v = VariantFischeRandom;
2015       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2016                  (i = 1, p = StrCaseStr(e, "w"))) {
2017         p += i;
2018         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2019         if (isdigit(*p)) {
2020           wnum = atoi(p);
2021         } else {
2022           wnum = -1;
2023         }
2024         switch (wnum) {
2025         case 0: /* FICS only, actually */
2026         case 1:
2027           /* Castling legal even if K starts on d-file */
2028           v = VariantWildCastle;
2029           break;
2030         case 2:
2031         case 3:
2032         case 4:
2033           /* Castling illegal even if K & R happen to start in
2034              normal positions. */
2035           v = VariantNoCastle;
2036           break;
2037         case 5:
2038         case 7:
2039         case 8:
2040         case 10:
2041         case 11:
2042         case 12:
2043         case 13:
2044         case 14:
2045         case 15:
2046         case 18:
2047         case 19:
2048           /* Castling legal iff K & R start in normal positions */
2049           v = VariantNormal;
2050           break;
2051         case 6:
2052         case 20:
2053         case 21:
2054           /* Special wilds for position setup; unclear what to do here */
2055           v = VariantLoadable;
2056           break;
2057         case 9:
2058           /* Bizarre ICC game */
2059           v = VariantTwoKings;
2060           break;
2061         case 16:
2062           v = VariantKriegspiel;
2063           break;
2064         case 17:
2065           v = VariantLosers;
2066           break;
2067         case 22:
2068           v = VariantFischeRandom;
2069           break;
2070         case 23:
2071           v = VariantCrazyhouse;
2072           break;
2073         case 24:
2074           v = VariantBughouse;
2075           break;
2076         case 25:
2077           v = Variant3Check;
2078           break;
2079         case 26:
2080           /* Not quite the same as FICS suicide! */
2081           v = VariantGiveaway;
2082           break;
2083         case 27:
2084           v = VariantAtomic;
2085           break;
2086         case 28:
2087           v = VariantShatranj;
2088           break;
2089
2090         /* Temporary names for future ICC types.  The name *will* change in
2091            the next xboard/WinBoard release after ICC defines it. */
2092         case 29:
2093           v = Variant29;
2094           break;
2095         case 30:
2096           v = Variant30;
2097           break;
2098         case 31:
2099           v = Variant31;
2100           break;
2101         case 32:
2102           v = Variant32;
2103           break;
2104         case 33:
2105           v = Variant33;
2106           break;
2107         case 34:
2108           v = Variant34;
2109           break;
2110         case 35:
2111           v = Variant35;
2112           break;
2113         case 36:
2114           v = Variant36;
2115           break;
2116         case 37:
2117           v = VariantShogi;
2118           break;
2119         case 38:
2120           v = VariantXiangqi;
2121           break;
2122         case 39:
2123           v = VariantCourier;
2124           break;
2125         case 40:
2126           v = VariantGothic;
2127           break;
2128         case 41:
2129           v = VariantCapablanca;
2130           break;
2131         case 42:
2132           v = VariantKnightmate;
2133           break;
2134         case 43:
2135           v = VariantFairy;
2136           break;
2137         case 44:
2138           v = VariantCylinder;
2139           break;
2140         case 45:
2141           v = VariantFalcon;
2142           break;
2143         case 46:
2144           v = VariantCapaRandom;
2145           break;
2146         case 47:
2147           v = VariantBerolina;
2148           break;
2149         case 48:
2150           v = VariantJanus;
2151           break;
2152         case 49:
2153           v = VariantSuper;
2154           break;
2155         case 50:
2156           v = VariantGreat;
2157           break;
2158         case -1:
2159           /* Found "wild" or "w" in the string but no number;
2160              must assume it's normal chess. */
2161           v = VariantNormal;
2162           break;
2163         default:
2164           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2165           if( (len >= MSG_SIZ) && appData.debugMode )
2166             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2167
2168           DisplayError(buf, 0);
2169           v = VariantUnknown;
2170           break;
2171         }
2172       }
2173     }
2174     if (appData.debugMode) {
2175       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2176               e, wnum, VariantName(v));
2177     }
2178     return v;
2179 }
2180
2181 static int leftover_start = 0, leftover_len = 0;
2182 char star_match[STAR_MATCH_N][MSG_SIZ];
2183
2184 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2185    advance *index beyond it, and set leftover_start to the new value of
2186    *index; else return FALSE.  If pattern contains the character '*', it
2187    matches any sequence of characters not containing '\r', '\n', or the
2188    character following the '*' (if any), and the matched sequence(s) are
2189    copied into star_match.
2190    */
2191 int
2192 looking_at ( char *buf, int *index, char *pattern)
2193 {
2194     char *bufp = &buf[*index], *patternp = pattern;
2195     int star_count = 0;
2196     char *matchp = star_match[0];
2197
2198     for (;;) {
2199         if (*patternp == NULLCHAR) {
2200             *index = leftover_start = bufp - buf;
2201             *matchp = NULLCHAR;
2202             return TRUE;
2203         }
2204         if (*bufp == NULLCHAR) return FALSE;
2205         if (*patternp == '*') {
2206             if (*bufp == *(patternp + 1)) {
2207                 *matchp = NULLCHAR;
2208                 matchp = star_match[++star_count];
2209                 patternp += 2;
2210                 bufp++;
2211                 continue;
2212             } else if (*bufp == '\n' || *bufp == '\r') {
2213                 patternp++;
2214                 if (*patternp == NULLCHAR)
2215                   continue;
2216                 else
2217                   return FALSE;
2218             } else {
2219                 *matchp++ = *bufp++;
2220                 continue;
2221             }
2222         }
2223         if (*patternp != *bufp) return FALSE;
2224         patternp++;
2225         bufp++;
2226     }
2227 }
2228
2229 void
2230 SendToPlayer (char *data, int length)
2231 {
2232     int error, outCount;
2233     outCount = OutputToProcess(NoProc, data, length, &error);
2234     if (outCount < length) {
2235         DisplayFatalError(_("Error writing to display"), error, 1);
2236     }
2237 }
2238
2239 void
2240 PackHolding (char packed[], char *holding)
2241 {
2242     char *p = holding;
2243     char *q = packed;
2244     int runlength = 0;
2245     int curr = 9999;
2246     do {
2247         if (*p == curr) {
2248             runlength++;
2249         } else {
2250             switch (runlength) {
2251               case 0:
2252                 break;
2253               case 1:
2254                 *q++ = curr;
2255                 break;
2256               case 2:
2257                 *q++ = curr;
2258                 *q++ = curr;
2259                 break;
2260               default:
2261                 sprintf(q, "%d", runlength);
2262                 while (*q) q++;
2263                 *q++ = curr;
2264                 break;
2265             }
2266             runlength = 1;
2267             curr = *p;
2268         }
2269     } while (*p++);
2270     *q = NULLCHAR;
2271 }
2272
2273 /* Telnet protocol requests from the front end */
2274 void
2275 TelnetRequest (unsigned char ddww, unsigned char option)
2276 {
2277     unsigned char msg[3];
2278     int outCount, outError;
2279
2280     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2281
2282     if (appData.debugMode) {
2283         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2284         switch (ddww) {
2285           case TN_DO:
2286             ddwwStr = "DO";
2287             break;
2288           case TN_DONT:
2289             ddwwStr = "DONT";
2290             break;
2291           case TN_WILL:
2292             ddwwStr = "WILL";
2293             break;
2294           case TN_WONT:
2295             ddwwStr = "WONT";
2296             break;
2297           default:
2298             ddwwStr = buf1;
2299             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2300             break;
2301         }
2302         switch (option) {
2303           case TN_ECHO:
2304             optionStr = "ECHO";
2305             break;
2306           default:
2307             optionStr = buf2;
2308             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2309             break;
2310         }
2311         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2312     }
2313     msg[0] = TN_IAC;
2314     msg[1] = ddww;
2315     msg[2] = option;
2316     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2317     if (outCount < 3) {
2318         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2319     }
2320 }
2321
2322 void
2323 DoEcho ()
2324 {
2325     if (!appData.icsActive) return;
2326     TelnetRequest(TN_DO, TN_ECHO);
2327 }
2328
2329 void
2330 DontEcho ()
2331 {
2332     if (!appData.icsActive) return;
2333     TelnetRequest(TN_DONT, TN_ECHO);
2334 }
2335
2336 void
2337 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2338 {
2339     /* put the holdings sent to us by the server on the board holdings area */
2340     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2341     char p;
2342     ChessSquare piece;
2343
2344     if(gameInfo.holdingsWidth < 2)  return;
2345     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2346         return; // prevent overwriting by pre-board holdings
2347
2348     if( (int)lowestPiece >= BlackPawn ) {
2349         holdingsColumn = 0;
2350         countsColumn = 1;
2351         holdingsStartRow = BOARD_HEIGHT-1;
2352         direction = -1;
2353     } else {
2354         holdingsColumn = BOARD_WIDTH-1;
2355         countsColumn = BOARD_WIDTH-2;
2356         holdingsStartRow = 0;
2357         direction = 1;
2358     }
2359
2360     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2361         board[i][holdingsColumn] = EmptySquare;
2362         board[i][countsColumn]   = (ChessSquare) 0;
2363     }
2364     while( (p=*holdings++) != NULLCHAR ) {
2365         piece = CharToPiece( ToUpper(p) );
2366         if(piece == EmptySquare) continue;
2367         /*j = (int) piece - (int) WhitePawn;*/
2368         j = PieceToNumber(piece);
2369         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2370         if(j < 0) continue;               /* should not happen */
2371         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2372         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2373         board[holdingsStartRow+j*direction][countsColumn]++;
2374     }
2375 }
2376
2377
2378 void
2379 VariantSwitch (Board board, VariantClass newVariant)
2380 {
2381    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2382    static Board oldBoard;
2383
2384    startedFromPositionFile = FALSE;
2385    if(gameInfo.variant == newVariant) return;
2386
2387    /* [HGM] This routine is called each time an assignment is made to
2388     * gameInfo.variant during a game, to make sure the board sizes
2389     * are set to match the new variant. If that means adding or deleting
2390     * holdings, we shift the playing board accordingly
2391     * This kludge is needed because in ICS observe mode, we get boards
2392     * of an ongoing game without knowing the variant, and learn about the
2393     * latter only later. This can be because of the move list we requested,
2394     * in which case the game history is refilled from the beginning anyway,
2395     * but also when receiving holdings of a crazyhouse game. In the latter
2396     * case we want to add those holdings to the already received position.
2397     */
2398
2399
2400    if (appData.debugMode) {
2401      fprintf(debugFP, "Switch board from %s to %s\n",
2402              VariantName(gameInfo.variant), VariantName(newVariant));
2403      setbuf(debugFP, NULL);
2404    }
2405    shuffleOpenings = 0;       /* [HGM] shuffle */
2406    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2407    switch(newVariant)
2408      {
2409      case VariantShogi:
2410        newWidth = 9;  newHeight = 9;
2411        gameInfo.holdingsSize = 7;
2412      case VariantBughouse:
2413      case VariantCrazyhouse:
2414        newHoldingsWidth = 2; break;
2415      case VariantGreat:
2416        newWidth = 10;
2417      case VariantSuper:
2418        newHoldingsWidth = 2;
2419        gameInfo.holdingsSize = 8;
2420        break;
2421      case VariantGothic:
2422      case VariantCapablanca:
2423      case VariantCapaRandom:
2424        newWidth = 10;
2425      default:
2426        newHoldingsWidth = gameInfo.holdingsSize = 0;
2427      };
2428
2429    if(newWidth  != gameInfo.boardWidth  ||
2430       newHeight != gameInfo.boardHeight ||
2431       newHoldingsWidth != gameInfo.holdingsWidth ) {
2432
2433      /* shift position to new playing area, if needed */
2434      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2435        for(i=0; i<BOARD_HEIGHT; i++)
2436          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2437            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2438              board[i][j];
2439        for(i=0; i<newHeight; i++) {
2440          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2441          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2442        }
2443      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2444        for(i=0; i<BOARD_HEIGHT; i++)
2445          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2446            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2447              board[i][j];
2448      }
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.) tc = 1.;
2497         if(tc > 95.) tc = 95.;
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          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4246       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4247       char *toSqr;
4248       for (k = 0; k < ranks; k++) {
4249         for (j = 0; j < files; j++)
4250           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4251         if(gameInfo.holdingsWidth > 1) {
4252              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4253              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4254         }
4255       }
4256       CopyBoard(partnerBoard, board);
4257       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4258         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4259         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4260       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4261       if(toSqr = strchr(str, '-')) {
4262         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4263         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4264       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4265       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4266       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4267       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4268       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4269       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4270                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4271       DisplayMessage(partnerStatus, "");
4272         partnerBoardValid = TRUE;
4273       return;
4274     }
4275
4276     /* Modify behavior for initial board display on move listing
4277        of wild games.
4278        */
4279     switch (ics_getting_history) {
4280       case H_FALSE:
4281       case H_REQUESTED:
4282         break;
4283       case H_GOT_REQ_HEADER:
4284       case H_GOT_UNREQ_HEADER:
4285         /* This is the initial position of the current game */
4286         gamenum = ics_gamenum;
4287         moveNum = 0;            /* old ICS bug workaround */
4288         if (to_play == 'B') {
4289           startedFromSetupPosition = TRUE;
4290           blackPlaysFirst = TRUE;
4291           moveNum = 1;
4292           if (forwardMostMove == 0) forwardMostMove = 1;
4293           if (backwardMostMove == 0) backwardMostMove = 1;
4294           if (currentMove == 0) currentMove = 1;
4295         }
4296         newGameMode = gameMode;
4297         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4298         break;
4299       case H_GOT_UNWANTED_HEADER:
4300         /* This is an initial board that we don't want */
4301         return;
4302       case H_GETTING_MOVES:
4303         /* Should not happen */
4304         DisplayError(_("Error gathering move list: extra board"), 0);
4305         ics_getting_history = H_FALSE;
4306         return;
4307     }
4308
4309    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4310                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4311      /* [HGM] We seem to have switched variant unexpectedly
4312       * Try to guess new variant from board size
4313       */
4314           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4315           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4316           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4317           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4318           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4319           if(!weird) newVariant = VariantNormal;
4320           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4321           /* Get a move list just to see the header, which
4322              will tell us whether this is really bug or zh */
4323           if (ics_getting_history == H_FALSE) {
4324             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4325             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4326             SendToICS(str);
4327           }
4328     }
4329
4330     /* Take action if this is the first board of a new game, or of a
4331        different game than is currently being displayed.  */
4332     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4333         relation == RELATION_ISOLATED_BOARD) {
4334
4335         /* Forget the old game and get the history (if any) of the new one */
4336         if (gameMode != BeginningOfGame) {
4337           Reset(TRUE, TRUE);
4338         }
4339         newGame = TRUE;
4340         if (appData.autoRaiseBoard) BoardToTop();
4341         prevMove = -3;
4342         if (gamenum == -1) {
4343             newGameMode = IcsIdle;
4344         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4345                    appData.getMoveList && !reqFlag) {
4346             /* Need to get game history */
4347             ics_getting_history = H_REQUESTED;
4348             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4349             SendToICS(str);
4350         }
4351
4352         /* Initially flip the board to have black on the bottom if playing
4353            black or if the ICS flip flag is set, but let the user change
4354            it with the Flip View button. */
4355         flipView = appData.autoFlipView ?
4356           (newGameMode == IcsPlayingBlack) || ics_flip :
4357           appData.flipView;
4358
4359         /* Done with values from previous mode; copy in new ones */
4360         gameMode = newGameMode;
4361         ModeHighlight();
4362         ics_gamenum = gamenum;
4363         if (gamenum == gs_gamenum) {
4364             int klen = strlen(gs_kind);
4365             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4366             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4367             gameInfo.event = StrSave(str);
4368         } else {
4369             gameInfo.event = StrSave("ICS game");
4370         }
4371         gameInfo.site = StrSave(appData.icsHost);
4372         gameInfo.date = PGNDate();
4373         gameInfo.round = StrSave("-");
4374         gameInfo.white = StrSave(white);
4375         gameInfo.black = StrSave(black);
4376         timeControl = basetime * 60 * 1000;
4377         timeControl_2 = 0;
4378         timeIncrement = increment * 1000;
4379         movesPerSession = 0;
4380         gameInfo.timeControl = TimeControlTagValue();
4381         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4382   if (appData.debugMode) {
4383     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4384     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4385     setbuf(debugFP, NULL);
4386   }
4387
4388         gameInfo.outOfBook = NULL;
4389
4390         /* Do we have the ratings? */
4391         if (strcmp(player1Name, white) == 0 &&
4392             strcmp(player2Name, black) == 0) {
4393             if (appData.debugMode)
4394               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4395                       player1Rating, player2Rating);
4396             gameInfo.whiteRating = player1Rating;
4397             gameInfo.blackRating = player2Rating;
4398         } else if (strcmp(player2Name, white) == 0 &&
4399                    strcmp(player1Name, black) == 0) {
4400             if (appData.debugMode)
4401               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4402                       player2Rating, player1Rating);
4403             gameInfo.whiteRating = player2Rating;
4404             gameInfo.blackRating = player1Rating;
4405         }
4406         player1Name[0] = player2Name[0] = NULLCHAR;
4407
4408         /* Silence shouts if requested */
4409         if (appData.quietPlay &&
4410             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4411             SendToICS(ics_prefix);
4412             SendToICS("set shout 0\n");
4413         }
4414     }
4415
4416     /* Deal with midgame name changes */
4417     if (!newGame) {
4418         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4419             if (gameInfo.white) free(gameInfo.white);
4420             gameInfo.white = StrSave(white);
4421         }
4422         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4423             if (gameInfo.black) free(gameInfo.black);
4424             gameInfo.black = StrSave(black);
4425         }
4426     }
4427
4428     /* Throw away game result if anything actually changes in examine mode */
4429     if (gameMode == IcsExamining && !newGame) {
4430         gameInfo.result = GameUnfinished;
4431         if (gameInfo.resultDetails != NULL) {
4432             free(gameInfo.resultDetails);
4433             gameInfo.resultDetails = NULL;
4434         }
4435     }
4436
4437     /* In pausing && IcsExamining mode, we ignore boards coming
4438        in if they are in a different variation than we are. */
4439     if (pauseExamInvalid) return;
4440     if (pausing && gameMode == IcsExamining) {
4441         if (moveNum <= pauseExamForwardMostMove) {
4442             pauseExamInvalid = TRUE;
4443             forwardMostMove = pauseExamForwardMostMove;
4444             return;
4445         }
4446     }
4447
4448   if (appData.debugMode) {
4449     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4450   }
4451     /* Parse the board */
4452     for (k = 0; k < ranks; k++) {
4453       for (j = 0; j < files; j++)
4454         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4455       if(gameInfo.holdingsWidth > 1) {
4456            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4457            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4458       }
4459     }
4460     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4461       board[5][BOARD_RGHT+1] = WhiteAngel;
4462       board[6][BOARD_RGHT+1] = WhiteMarshall;
4463       board[1][0] = BlackMarshall;
4464       board[2][0] = BlackAngel;
4465       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4466     }
4467     CopyBoard(boards[moveNum], board);
4468     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4469     if (moveNum == 0) {
4470         startedFromSetupPosition =
4471           !CompareBoards(board, initialPosition);
4472         if(startedFromSetupPosition)
4473             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4474     }
4475
4476     /* [HGM] Set castling rights. Take the outermost Rooks,
4477        to make it also work for FRC opening positions. Note that board12
4478        is really defective for later FRC positions, as it has no way to
4479        indicate which Rook can castle if they are on the same side of King.
4480        For the initial position we grant rights to the outermost Rooks,
4481        and remember thos rights, and we then copy them on positions
4482        later in an FRC game. This means WB might not recognize castlings with
4483        Rooks that have moved back to their original position as illegal,
4484        but in ICS mode that is not its job anyway.
4485     */
4486     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4487     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4488
4489         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4490             if(board[0][i] == WhiteRook) j = i;
4491         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4492         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4493             if(board[0][i] == WhiteRook) j = i;
4494         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4495         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4496             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4497         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4498         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4499             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4500         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4501
4502         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4503         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4504         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4505             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4506         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4507             if(board[BOARD_HEIGHT-1][k] == bKing)
4508                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4509         if(gameInfo.variant == VariantTwoKings) {
4510             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4511             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4512             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4513         }
4514     } else { int r;
4515         r = boards[moveNum][CASTLING][0] = initialRights[0];
4516         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4517         r = boards[moveNum][CASTLING][1] = initialRights[1];
4518         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4519         r = boards[moveNum][CASTLING][3] = initialRights[3];
4520         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4521         r = boards[moveNum][CASTLING][4] = initialRights[4];
4522         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4523         /* wildcastle kludge: always assume King has rights */
4524         r = boards[moveNum][CASTLING][2] = initialRights[2];
4525         r = boards[moveNum][CASTLING][5] = initialRights[5];
4526     }
4527     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4528     boards[moveNum][EP_STATUS] = EP_NONE;
4529     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4530     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4531     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4532
4533
4534     if (ics_getting_history == H_GOT_REQ_HEADER ||
4535         ics_getting_history == H_GOT_UNREQ_HEADER) {
4536         /* This was an initial position from a move list, not
4537            the current position */
4538         return;
4539     }
4540
4541     /* Update currentMove and known move number limits */
4542     newMove = newGame || moveNum > forwardMostMove;
4543
4544     if (newGame) {
4545         forwardMostMove = backwardMostMove = currentMove = moveNum;
4546         if (gameMode == IcsExamining && moveNum == 0) {
4547           /* Workaround for ICS limitation: we are not told the wild
4548              type when starting to examine a game.  But if we ask for
4549              the move list, the move list header will tell us */
4550             ics_getting_history = H_REQUESTED;
4551             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4552             SendToICS(str);
4553         }
4554     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4555                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4556 #if ZIPPY
4557         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4558         /* [HGM] applied this also to an engine that is silently watching        */
4559         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4560             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4561             gameInfo.variant == currentlyInitializedVariant) {
4562           takeback = forwardMostMove - moveNum;
4563           for (i = 0; i < takeback; i++) {
4564             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4565             SendToProgram("undo\n", &first);
4566           }
4567         }
4568 #endif
4569
4570         forwardMostMove = moveNum;
4571         if (!pausing || currentMove > forwardMostMove)
4572           currentMove = forwardMostMove;
4573     } else {
4574         /* New part of history that is not contiguous with old part */
4575         if (pausing && gameMode == IcsExamining) {
4576             pauseExamInvalid = TRUE;
4577             forwardMostMove = pauseExamForwardMostMove;
4578             return;
4579         }
4580         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4581 #if ZIPPY
4582             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4583                 // [HGM] when we will receive the move list we now request, it will be
4584                 // fed to the engine from the first move on. So if the engine is not
4585                 // in the initial position now, bring it there.
4586                 InitChessProgram(&first, 0);
4587             }
4588 #endif
4589             ics_getting_history = H_REQUESTED;
4590             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4591             SendToICS(str);
4592         }
4593         forwardMostMove = backwardMostMove = currentMove = moveNum;
4594     }
4595
4596     /* Update the clocks */
4597     if (strchr(elapsed_time, '.')) {
4598       /* Time is in ms */
4599       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4600       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4601     } else {
4602       /* Time is in seconds */
4603       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4604       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4605     }
4606
4607
4608 #if ZIPPY
4609     if (appData.zippyPlay && newGame &&
4610         gameMode != IcsObserving && gameMode != IcsIdle &&
4611         gameMode != IcsExamining)
4612       ZippyFirstBoard(moveNum, basetime, increment);
4613 #endif
4614
4615     /* Put the move on the move list, first converting
4616        to canonical algebraic form. */
4617     if (moveNum > 0) {
4618   if (appData.debugMode) {
4619     if (appData.debugMode) { int f = forwardMostMove;
4620         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4621                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4622                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4623     }
4624     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4625     fprintf(debugFP, "moveNum = %d\n", moveNum);
4626     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4627     setbuf(debugFP, NULL);
4628   }
4629         if (moveNum <= backwardMostMove) {
4630             /* We don't know what the board looked like before
4631                this move.  Punt. */
4632           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4633             strcat(parseList[moveNum - 1], " ");
4634             strcat(parseList[moveNum - 1], elapsed_time);
4635             moveList[moveNum - 1][0] = NULLCHAR;
4636         } else if (strcmp(move_str, "none") == 0) {
4637             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4638             /* Again, we don't know what the board looked like;
4639                this is really the start of the game. */
4640             parseList[moveNum - 1][0] = NULLCHAR;
4641             moveList[moveNum - 1][0] = NULLCHAR;
4642             backwardMostMove = moveNum;
4643             startedFromSetupPosition = TRUE;
4644             fromX = fromY = toX = toY = -1;
4645         } else {
4646           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4647           //                 So we parse the long-algebraic move string in stead of the SAN move
4648           int valid; char buf[MSG_SIZ], *prom;
4649
4650           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4651                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4652           // str looks something like "Q/a1-a2"; kill the slash
4653           if(str[1] == '/')
4654             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4655           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4656           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4657                 strcat(buf, prom); // long move lacks promo specification!
4658           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4659                 if(appData.debugMode)
4660                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4661                 safeStrCpy(move_str, buf, MSG_SIZ);
4662           }
4663           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4664                                 &fromX, &fromY, &toX, &toY, &promoChar)
4665                || ParseOneMove(buf, moveNum - 1, &moveType,
4666                                 &fromX, &fromY, &toX, &toY, &promoChar);
4667           // end of long SAN patch
4668           if (valid) {
4669             (void) CoordsToAlgebraic(boards[moveNum - 1],
4670                                      PosFlags(moveNum - 1),
4671                                      fromY, fromX, toY, toX, promoChar,
4672                                      parseList[moveNum-1]);
4673             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4674               case MT_NONE:
4675               case MT_STALEMATE:
4676               default:
4677                 break;
4678               case MT_CHECK:
4679                 if(gameInfo.variant != VariantShogi)
4680                     strcat(parseList[moveNum - 1], "+");
4681                 break;
4682               case MT_CHECKMATE:
4683               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4684                 strcat(parseList[moveNum - 1], "#");
4685                 break;
4686             }
4687             strcat(parseList[moveNum - 1], " ");
4688             strcat(parseList[moveNum - 1], elapsed_time);
4689             /* currentMoveString is set as a side-effect of ParseOneMove */
4690             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4691             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4692             strcat(moveList[moveNum - 1], "\n");
4693
4694             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4695                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4696               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4697                 ChessSquare old, new = boards[moveNum][k][j];
4698                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4699                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4700                   if(old == new) continue;
4701                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4702                   else if(new == WhiteWazir || new == BlackWazir) {
4703                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4704                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4705                       else boards[moveNum][k][j] = old; // preserve type of Gold
4706                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4707                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4708               }
4709           } else {
4710             /* Move from ICS was illegal!?  Punt. */
4711             if (appData.debugMode) {
4712               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4713               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4714             }
4715             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4716             strcat(parseList[moveNum - 1], " ");
4717             strcat(parseList[moveNum - 1], elapsed_time);
4718             moveList[moveNum - 1][0] = NULLCHAR;
4719             fromX = fromY = toX = toY = -1;
4720           }
4721         }
4722   if (appData.debugMode) {
4723     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4724     setbuf(debugFP, NULL);
4725   }
4726
4727 #if ZIPPY
4728         /* Send move to chess program (BEFORE animating it). */
4729         if (appData.zippyPlay && !newGame && newMove &&
4730            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4731
4732             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4733                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4734                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4735                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4736                             move_str);
4737                     DisplayError(str, 0);
4738                 } else {
4739                     if (first.sendTime) {
4740                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4741                     }
4742                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4743                     if (firstMove && !bookHit) {
4744                         firstMove = FALSE;
4745                         if (first.useColors) {
4746                           SendToProgram(gameMode == IcsPlayingWhite ?
4747                                         "white\ngo\n" :
4748                                         "black\ngo\n", &first);
4749                         } else {
4750                           SendToProgram("go\n", &first);
4751                         }
4752                         first.maybeThinking = TRUE;
4753                     }
4754                 }
4755             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4756               if (moveList[moveNum - 1][0] == NULLCHAR) {
4757                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4758                 DisplayError(str, 0);
4759               } else {
4760                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4761                 SendMoveToProgram(moveNum - 1, &first);
4762               }
4763             }
4764         }
4765 #endif
4766     }
4767
4768     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4769         /* If move comes from a remote source, animate it.  If it
4770            isn't remote, it will have already been animated. */
4771         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4772             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4773         }
4774         if (!pausing && appData.highlightLastMove) {
4775             SetHighlights(fromX, fromY, toX, toY);
4776         }
4777     }
4778
4779     /* Start the clocks */
4780     whiteFlag = blackFlag = FALSE;
4781     appData.clockMode = !(basetime == 0 && increment == 0);
4782     if (ticking == 0) {
4783       ics_clock_paused = TRUE;
4784       StopClocks();
4785     } else if (ticking == 1) {
4786       ics_clock_paused = FALSE;
4787     }
4788     if (gameMode == IcsIdle ||
4789         relation == RELATION_OBSERVING_STATIC ||
4790         relation == RELATION_EXAMINING ||
4791         ics_clock_paused)
4792       DisplayBothClocks();
4793     else
4794       StartClocks();
4795
4796     /* Display opponents and material strengths */
4797     if (gameInfo.variant != VariantBughouse &&
4798         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4799         if (tinyLayout || smallLayout) {
4800             if(gameInfo.variant == VariantNormal)
4801               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4802                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4803                     basetime, increment);
4804             else
4805               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4806                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4807                     basetime, increment, (int) gameInfo.variant);
4808         } else {
4809             if(gameInfo.variant == VariantNormal)
4810               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4811                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4812                     basetime, increment);
4813             else
4814               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4815                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4816                     basetime, increment, VariantName(gameInfo.variant));
4817         }
4818         DisplayTitle(str);
4819   if (appData.debugMode) {
4820     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4821   }
4822     }
4823
4824
4825     /* Display the board */
4826     if (!pausing && !appData.noGUI) {
4827
4828       if (appData.premove)
4829           if (!gotPremove ||
4830              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4831              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4832               ClearPremoveHighlights();
4833
4834       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4835         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4836       DrawPosition(j, boards[currentMove]);
4837
4838       DisplayMove(moveNum - 1);
4839       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4840             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4841               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4842         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4843       }
4844     }
4845
4846     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4847 #if ZIPPY
4848     if(bookHit) { // [HGM] book: simulate book reply
4849         static char bookMove[MSG_SIZ]; // a bit generous?
4850
4851         programStats.nodes = programStats.depth = programStats.time =
4852         programStats.score = programStats.got_only_move = 0;
4853         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4854
4855         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4856         strcat(bookMove, bookHit);
4857         HandleMachineMove(bookMove, &first);
4858     }
4859 #endif
4860 }
4861
4862 void
4863 GetMoveListEvent ()
4864 {
4865     char buf[MSG_SIZ];
4866     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4867         ics_getting_history = H_REQUESTED;
4868         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4869         SendToICS(buf);
4870     }
4871 }
4872
4873 void
4874 AnalysisPeriodicEvent (int force)
4875 {
4876     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4877          && !force) || !appData.periodicUpdates)
4878       return;
4879
4880     /* Send . command to Crafty to collect stats */
4881     SendToProgram(".\n", &first);
4882
4883     /* Don't send another until we get a response (this makes
4884        us stop sending to old Crafty's which don't understand
4885        the "." command (sending illegal cmds resets node count & time,
4886        which looks bad)) */
4887     programStats.ok_to_send = 0;
4888 }
4889
4890 void
4891 ics_update_width (int new_width)
4892 {
4893         ics_printf("set width %d\n", new_width);
4894 }
4895
4896 void
4897 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4898 {
4899     char buf[MSG_SIZ];
4900
4901     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4902         // null move in variant where engine does not understand it (for analysis purposes)
4903         SendBoard(cps, moveNum + 1); // send position after move in stead.
4904         return;
4905     }
4906     if (cps->useUsermove) {
4907       SendToProgram("usermove ", cps);
4908     }
4909     if (cps->useSAN) {
4910       char *space;
4911       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4912         int len = space - parseList[moveNum];
4913         memcpy(buf, parseList[moveNum], len);
4914         buf[len++] = '\n';
4915         buf[len] = NULLCHAR;
4916       } else {
4917         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4918       }
4919       SendToProgram(buf, cps);
4920     } else {
4921       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4922         AlphaRank(moveList[moveNum], 4);
4923         SendToProgram(moveList[moveNum], cps);
4924         AlphaRank(moveList[moveNum], 4); // and back
4925       } else
4926       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4927        * the engine. It would be nice to have a better way to identify castle
4928        * moves here. */
4929       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4930                                                                          && cps->useOOCastle) {
4931         int fromX = moveList[moveNum][0] - AAA;
4932         int fromY = moveList[moveNum][1] - ONE;
4933         int toX = moveList[moveNum][2] - AAA;
4934         int toY = moveList[moveNum][3] - ONE;
4935         if((boards[moveNum][fromY][fromX] == WhiteKing
4936             && boards[moveNum][toY][toX] == WhiteRook)
4937            || (boards[moveNum][fromY][fromX] == BlackKing
4938                && boards[moveNum][toY][toX] == BlackRook)) {
4939           if(toX > fromX) SendToProgram("O-O\n", cps);
4940           else SendToProgram("O-O-O\n", cps);
4941         }
4942         else SendToProgram(moveList[moveNum], cps);
4943       } else
4944       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4945         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4946           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4947           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4948                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4949         } else
4950           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4951                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4952         SendToProgram(buf, cps);
4953       }
4954       else SendToProgram(moveList[moveNum], cps);
4955       /* End of additions by Tord */
4956     }
4957
4958     /* [HGM] setting up the opening has brought engine in force mode! */
4959     /*       Send 'go' if we are in a mode where machine should play. */
4960     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4961         (gameMode == TwoMachinesPlay   ||
4962 #if ZIPPY
4963          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4964 #endif
4965          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4966         SendToProgram("go\n", cps);
4967   if (appData.debugMode) {
4968     fprintf(debugFP, "(extra)\n");
4969   }
4970     }
4971     setboardSpoiledMachineBlack = 0;
4972 }
4973
4974 void
4975 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4976 {
4977     char user_move[MSG_SIZ];
4978     char suffix[4];
4979
4980     if(gameInfo.variant == VariantSChess && promoChar) {
4981         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4982         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4983     } else suffix[0] = NULLCHAR;
4984
4985     switch (moveType) {
4986       default:
4987         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4988                 (int)moveType, fromX, fromY, toX, toY);
4989         DisplayError(user_move + strlen("say "), 0);
4990         break;
4991       case WhiteKingSideCastle:
4992       case BlackKingSideCastle:
4993       case WhiteQueenSideCastleWild:
4994       case BlackQueenSideCastleWild:
4995       /* PUSH Fabien */
4996       case WhiteHSideCastleFR:
4997       case BlackHSideCastleFR:
4998       /* POP Fabien */
4999         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5000         break;
5001       case WhiteQueenSideCastle:
5002       case BlackQueenSideCastle:
5003       case WhiteKingSideCastleWild:
5004       case BlackKingSideCastleWild:
5005       /* PUSH Fabien */
5006       case WhiteASideCastleFR:
5007       case BlackASideCastleFR:
5008       /* POP Fabien */
5009         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5010         break;
5011       case WhiteNonPromotion:
5012       case BlackNonPromotion:
5013         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5014         break;
5015       case WhitePromotion:
5016       case BlackPromotion:
5017         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5018           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5019                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5020                 PieceToChar(WhiteFerz));
5021         else if(gameInfo.variant == VariantGreat)
5022           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5023                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5024                 PieceToChar(WhiteMan));
5025         else
5026           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5027                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5028                 promoChar);
5029         break;
5030       case WhiteDrop:
5031       case BlackDrop:
5032       drop:
5033         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5034                  ToUpper(PieceToChar((ChessSquare) fromX)),
5035                  AAA + toX, ONE + toY);
5036         break;
5037       case IllegalMove:  /* could be a variant we don't quite understand */
5038         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5039       case NormalMove:
5040       case WhiteCapturesEnPassant:
5041       case BlackCapturesEnPassant:
5042         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5043                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5044         break;
5045     }
5046     SendToICS(user_move);
5047     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5048         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5049 }
5050
5051 void
5052 UploadGameEvent ()
5053 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5054     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5055     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5056     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5057       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5058       return;
5059     }
5060     if(gameMode != IcsExamining) { // is this ever not the case?
5061         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5062
5063         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5064           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5065         } else { // on FICS we must first go to general examine mode
5066           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5067         }
5068         if(gameInfo.variant != VariantNormal) {
5069             // try figure out wild number, as xboard names are not always valid on ICS
5070             for(i=1; i<=36; i++) {
5071               snprintf(buf, MSG_SIZ, "wild/%d", i);
5072                 if(StringToVariant(buf) == gameInfo.variant) break;
5073             }
5074             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5075             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5076             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5077         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5078         SendToICS(ics_prefix);
5079         SendToICS(buf);
5080         if(startedFromSetupPosition || backwardMostMove != 0) {
5081           fen = PositionToFEN(backwardMostMove, NULL);
5082           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5083             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5084             SendToICS(buf);
5085           } else { // FICS: everything has to set by separate bsetup commands
5086             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5087             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5088             SendToICS(buf);
5089             if(!WhiteOnMove(backwardMostMove)) {
5090                 SendToICS("bsetup tomove black\n");
5091             }
5092             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5093             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5094             SendToICS(buf);
5095             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5096             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5097             SendToICS(buf);
5098             i = boards[backwardMostMove][EP_STATUS];
5099             if(i >= 0) { // set e.p.
5100               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5101                 SendToICS(buf);
5102             }
5103             bsetup++;
5104           }
5105         }
5106       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5107             SendToICS("bsetup done\n"); // switch to normal examining.
5108     }
5109     for(i = backwardMostMove; i<last; i++) {
5110         char buf[20];
5111         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5112         SendToICS(buf);
5113     }
5114     SendToICS(ics_prefix);
5115     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5116 }
5117
5118 void
5119 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5120 {
5121     if (rf == DROP_RANK) {
5122       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5123       sprintf(move, "%c@%c%c\n",
5124                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5125     } else {
5126         if (promoChar == 'x' || promoChar == NULLCHAR) {
5127           sprintf(move, "%c%c%c%c\n",
5128                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5129         } else {
5130             sprintf(move, "%c%c%c%c%c\n",
5131                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5132         }
5133     }
5134 }
5135
5136 void
5137 ProcessICSInitScript (FILE *f)
5138 {
5139     char buf[MSG_SIZ];
5140
5141     while (fgets(buf, MSG_SIZ, f)) {
5142         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5143     }
5144
5145     fclose(f);
5146 }
5147
5148
5149 static int lastX, lastY, selectFlag, dragging;
5150
5151 void
5152 Sweep (int step)
5153 {
5154     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5155     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5156     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5157     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5158     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5159     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5160     do {
5161         promoSweep -= step;
5162         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5163         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5164         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5165         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5166         if(!step) step = -1;
5167     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5168             appData.testLegality && (promoSweep == king ||
5169             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5170     ChangeDragPiece(promoSweep);
5171 }
5172
5173 int
5174 PromoScroll (int x, int y)
5175 {
5176   int step = 0;
5177
5178   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5179   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5180   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5181   if(!step) return FALSE;
5182   lastX = x; lastY = y;
5183   if((promoSweep < BlackPawn) == flipView) step = -step;
5184   if(step > 0) selectFlag = 1;
5185   if(!selectFlag) Sweep(step);
5186   return FALSE;
5187 }
5188
5189 void
5190 NextPiece (int step)
5191 {
5192     ChessSquare piece = boards[currentMove][toY][toX];
5193     do {
5194         pieceSweep -= step;
5195         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5196         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5197         if(!step) step = -1;
5198     } while(PieceToChar(pieceSweep) == '.');
5199     boards[currentMove][toY][toX] = pieceSweep;
5200     DrawPosition(FALSE, boards[currentMove]);
5201     boards[currentMove][toY][toX] = piece;
5202 }
5203 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5204 void
5205 AlphaRank (char *move, int n)
5206 {
5207 //    char *p = move, c; int x, y;
5208
5209     if (appData.debugMode) {
5210         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5211     }
5212
5213     if(move[1]=='*' &&
5214        move[2]>='0' && move[2]<='9' &&
5215        move[3]>='a' && move[3]<='x'    ) {
5216         move[1] = '@';
5217         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5218         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5219     } else
5220     if(move[0]>='0' && move[0]<='9' &&
5221        move[1]>='a' && move[1]<='x' &&
5222        move[2]>='0' && move[2]<='9' &&
5223        move[3]>='a' && move[3]<='x'    ) {
5224         /* input move, Shogi -> normal */
5225         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5226         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5227         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5228         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5229     } else
5230     if(move[1]=='@' &&
5231        move[3]>='0' && move[3]<='9' &&
5232        move[2]>='a' && move[2]<='x'    ) {
5233         move[1] = '*';
5234         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5235         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5236     } else
5237     if(
5238        move[0]>='a' && move[0]<='x' &&
5239        move[3]>='0' && move[3]<='9' &&
5240        move[2]>='a' && move[2]<='x'    ) {
5241          /* output move, normal -> Shogi */
5242         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5243         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5244         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5245         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5246         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5247     }
5248     if (appData.debugMode) {
5249         fprintf(debugFP, "   out = '%s'\n", move);
5250     }
5251 }
5252
5253 char yy_textstr[8000];
5254
5255 /* Parser for moves from gnuchess, ICS, or user typein box */
5256 Boolean
5257 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5258 {
5259     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5260
5261     switch (*moveType) {
5262       case WhitePromotion:
5263       case BlackPromotion:
5264       case WhiteNonPromotion:
5265       case BlackNonPromotion:
5266       case NormalMove:
5267       case WhiteCapturesEnPassant:
5268       case BlackCapturesEnPassant:
5269       case WhiteKingSideCastle:
5270       case WhiteQueenSideCastle:
5271       case BlackKingSideCastle:
5272       case BlackQueenSideCastle:
5273       case WhiteKingSideCastleWild:
5274       case WhiteQueenSideCastleWild:
5275       case BlackKingSideCastleWild:
5276       case BlackQueenSideCastleWild:
5277       /* Code added by Tord: */
5278       case WhiteHSideCastleFR:
5279       case WhiteASideCastleFR:
5280       case BlackHSideCastleFR:
5281       case BlackASideCastleFR:
5282       /* End of code added by Tord */
5283       case IllegalMove:         /* bug or odd chess variant */
5284         *fromX = currentMoveString[0] - AAA;
5285         *fromY = currentMoveString[1] - ONE;
5286         *toX = currentMoveString[2] - AAA;
5287         *toY = currentMoveString[3] - ONE;
5288         *promoChar = currentMoveString[4];
5289         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5290             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5291     if (appData.debugMode) {
5292         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5293     }
5294             *fromX = *fromY = *toX = *toY = 0;
5295             return FALSE;
5296         }
5297         if (appData.testLegality) {
5298           return (*moveType != IllegalMove);
5299         } else {
5300           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5301                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5302         }
5303
5304       case WhiteDrop:
5305       case BlackDrop:
5306         *fromX = *moveType == WhiteDrop ?
5307           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5308           (int) CharToPiece(ToLower(currentMoveString[0]));
5309         *fromY = DROP_RANK;
5310         *toX = currentMoveString[2] - AAA;
5311         *toY = currentMoveString[3] - ONE;
5312         *promoChar = NULLCHAR;
5313         return TRUE;
5314
5315       case AmbiguousMove:
5316       case ImpossibleMove:
5317       case EndOfFile:
5318       case ElapsedTime:
5319       case Comment:
5320       case PGNTag:
5321       case NAG:
5322       case WhiteWins:
5323       case BlackWins:
5324       case GameIsDrawn:
5325       default:
5326     if (appData.debugMode) {
5327         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5328     }
5329         /* bug? */
5330         *fromX = *fromY = *toX = *toY = 0;
5331         *promoChar = NULLCHAR;
5332         return FALSE;
5333     }
5334 }
5335
5336 Boolean pushed = FALSE;
5337 char *lastParseAttempt;
5338
5339 void
5340 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5341 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5342   int fromX, fromY, toX, toY; char promoChar;
5343   ChessMove moveType;
5344   Boolean valid;
5345   int nr = 0;
5346
5347   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5348     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5349     pushed = TRUE;
5350   }
5351   endPV = forwardMostMove;
5352   do {
5353     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5354     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5355     lastParseAttempt = pv;
5356     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5357     if(!valid && nr == 0 &&
5358        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5359         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5360         // Hande case where played move is different from leading PV move
5361         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5362         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5363         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5364         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5365           endPV += 2; // if position different, keep this
5366           moveList[endPV-1][0] = fromX + AAA;
5367           moveList[endPV-1][1] = fromY + ONE;
5368           moveList[endPV-1][2] = toX + AAA;
5369           moveList[endPV-1][3] = toY + ONE;
5370           parseList[endPV-1][0] = NULLCHAR;
5371           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5372         }
5373       }
5374     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5375     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5376     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5377     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5378         valid++; // allow comments in PV
5379         continue;
5380     }
5381     nr++;
5382     if(endPV+1 > framePtr) break; // no space, truncate
5383     if(!valid) break;
5384     endPV++;
5385     CopyBoard(boards[endPV], boards[endPV-1]);
5386     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5387     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5388     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5389     CoordsToAlgebraic(boards[endPV - 1],
5390                              PosFlags(endPV - 1),
5391                              fromY, fromX, toY, toX, promoChar,
5392                              parseList[endPV - 1]);
5393   } while(valid);
5394   if(atEnd == 2) return; // used hidden, for PV conversion
5395   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5396   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5397   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5398                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5399   DrawPosition(TRUE, boards[currentMove]);
5400 }
5401
5402 int
5403 MultiPV (ChessProgramState *cps)
5404 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5405         int i;
5406         for(i=0; i<cps->nrOptions; i++)
5407             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5408                 return i;
5409         return -1;
5410 }
5411
5412 Boolean
5413 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5414 {
5415         int startPV, multi, lineStart, origIndex = index;
5416         char *p, buf2[MSG_SIZ];
5417
5418         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5419         lastX = x; lastY = y;
5420         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5421         lineStart = startPV = index;
5422         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5423         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5424         index = startPV;
5425         do{ while(buf[index] && buf[index] != '\n') index++;
5426         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5427         buf[index] = 0;
5428         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5429                 int n = first.option[multi].value;
5430                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5431                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5432                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5433                 first.option[multi].value = n;
5434                 *start = *end = 0;
5435                 return FALSE;
5436         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5437                 ExcludeClick(origIndex - lineStart);
5438                 return FALSE;
5439         }
5440         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5441         *start = startPV; *end = index-1;
5442         return TRUE;
5443 }
5444
5445 char *
5446 PvToSAN (char *pv)
5447 {
5448         static char buf[10*MSG_SIZ];
5449         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5450         *buf = NULLCHAR;
5451         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5452         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5453         for(i = forwardMostMove; i<endPV; i++){
5454             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5455             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5456             k += strlen(buf+k);
5457         }
5458         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5459         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5460         endPV = savedEnd;
5461         return buf;
5462 }
5463
5464 Boolean
5465 LoadPV (int x, int y)
5466 { // called on right mouse click to load PV
5467   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5468   lastX = x; lastY = y;
5469   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5470   return TRUE;
5471 }
5472
5473 void
5474 UnLoadPV ()
5475 {
5476   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5477   if(endPV < 0) return;
5478   if(appData.autoCopyPV) CopyFENToClipboard();
5479   endPV = -1;
5480   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5481         Boolean saveAnimate = appData.animate;
5482         if(pushed) {
5483             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5484                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5485             } else storedGames--; // abandon shelved tail of original game
5486         }
5487         pushed = FALSE;
5488         forwardMostMove = currentMove;
5489         currentMove = oldFMM;
5490         appData.animate = FALSE;
5491         ToNrEvent(forwardMostMove);
5492         appData.animate = saveAnimate;
5493   }
5494   currentMove = forwardMostMove;
5495   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5496   ClearPremoveHighlights();
5497   DrawPosition(TRUE, boards[currentMove]);
5498 }
5499
5500 void
5501 MovePV (int x, int y, int h)
5502 { // step through PV based on mouse coordinates (called on mouse move)
5503   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5504
5505   // we must somehow check if right button is still down (might be released off board!)
5506   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5507   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5508   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5509   if(!step) return;
5510   lastX = x; lastY = y;
5511
5512   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5513   if(endPV < 0) return;
5514   if(y < margin) step = 1; else
5515   if(y > h - margin) step = -1;
5516   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5517   currentMove += step;
5518   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5519   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5520                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5521   DrawPosition(FALSE, boards[currentMove]);
5522 }
5523
5524
5525 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5526 // All positions will have equal probability, but the current method will not provide a unique
5527 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5528 #define DARK 1
5529 #define LITE 2
5530 #define ANY 3
5531
5532 int squaresLeft[4];
5533 int piecesLeft[(int)BlackPawn];
5534 int seed, nrOfShuffles;
5535
5536 void
5537 GetPositionNumber ()
5538 {       // sets global variable seed
5539         int i;
5540
5541         seed = appData.defaultFrcPosition;
5542         if(seed < 0) { // randomize based on time for negative FRC position numbers
5543                 for(i=0; i<50; i++) seed += random();
5544                 seed = random() ^ random() >> 8 ^ random() << 8;
5545                 if(seed<0) seed = -seed;
5546         }
5547 }
5548
5549 int
5550 put (Board board, int pieceType, int rank, int n, int shade)
5551 // put the piece on the (n-1)-th empty squares of the given shade
5552 {
5553         int i;
5554
5555         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5556                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5557                         board[rank][i] = (ChessSquare) pieceType;
5558                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5559                         squaresLeft[ANY]--;
5560                         piecesLeft[pieceType]--;
5561                         return i;
5562                 }
5563         }
5564         return -1;
5565 }
5566
5567
5568 void
5569 AddOnePiece (Board board, int pieceType, int rank, int shade)
5570 // calculate where the next piece goes, (any empty square), and put it there
5571 {
5572         int i;
5573
5574         i = seed % squaresLeft[shade];
5575         nrOfShuffles *= squaresLeft[shade];
5576         seed /= squaresLeft[shade];
5577         put(board, pieceType, rank, i, shade);
5578 }
5579
5580 void
5581 AddTwoPieces (Board board, int pieceType, int rank)
5582 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5583 {
5584         int i, n=squaresLeft[ANY], j=n-1, k;
5585
5586         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5587         i = seed % k;  // pick one
5588         nrOfShuffles *= k;
5589         seed /= k;
5590         while(i >= j) i -= j--;
5591         j = n - 1 - j; i += j;
5592         put(board, pieceType, rank, j, ANY);
5593         put(board, pieceType, rank, i, ANY);
5594 }
5595
5596 void
5597 SetUpShuffle (Board board, int number)
5598 {
5599         int i, p, first=1;
5600
5601         GetPositionNumber(); nrOfShuffles = 1;
5602
5603         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5604         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5605         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5606
5607         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5608
5609         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5610             p = (int) board[0][i];
5611             if(p < (int) BlackPawn) piecesLeft[p] ++;
5612             board[0][i] = EmptySquare;
5613         }
5614
5615         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5616             // shuffles restricted to allow normal castling put KRR first
5617             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5618                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5619             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5620                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5621             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5622                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5623             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5624                 put(board, WhiteRook, 0, 0, ANY);
5625             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5626         }
5627
5628         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5629             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5630             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5631                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5632                 while(piecesLeft[p] >= 2) {
5633                     AddOnePiece(board, p, 0, LITE);
5634                     AddOnePiece(board, p, 0, DARK);
5635                 }
5636                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5637             }
5638
5639         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5640             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5641             // but we leave King and Rooks for last, to possibly obey FRC restriction
5642             if(p == (int)WhiteRook) continue;
5643             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5644             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5645         }
5646
5647         // now everything is placed, except perhaps King (Unicorn) and Rooks
5648
5649         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5650             // Last King gets castling rights
5651             while(piecesLeft[(int)WhiteUnicorn]) {
5652                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5653                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5654             }
5655
5656             while(piecesLeft[(int)WhiteKing]) {
5657                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5658                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5659             }
5660
5661
5662         } else {
5663             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5664             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5665         }
5666
5667         // Only Rooks can be left; simply place them all
5668         while(piecesLeft[(int)WhiteRook]) {
5669                 i = put(board, WhiteRook, 0, 0, ANY);
5670                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5671                         if(first) {
5672                                 first=0;
5673                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5674                         }
5675                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5676                 }
5677         }
5678         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5679             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5680         }
5681
5682         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5683 }
5684
5685 int
5686 SetCharTable (char *table, const char * map)
5687 /* [HGM] moved here from winboard.c because of its general usefulness */
5688 /*       Basically a safe strcpy that uses the last character as King */
5689 {
5690     int result = FALSE; int NrPieces;
5691
5692     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5693                     && NrPieces >= 12 && !(NrPieces&1)) {
5694         int i; /* [HGM] Accept even length from 12 to 34 */
5695
5696         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5697         for( i=0; i<NrPieces/2-1; i++ ) {
5698             table[i] = map[i];
5699             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5700         }
5701         table[(int) WhiteKing]  = map[NrPieces/2-1];
5702         table[(int) BlackKing]  = map[NrPieces-1];
5703
5704         result = TRUE;
5705     }
5706
5707     return result;
5708 }
5709
5710 void
5711 Prelude (Board board)
5712 {       // [HGM] superchess: random selection of exo-pieces
5713         int i, j, k; ChessSquare p;
5714         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5715
5716         GetPositionNumber(); // use FRC position number
5717
5718         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5719             SetCharTable(pieceToChar, appData.pieceToCharTable);
5720             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5721                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5722         }
5723
5724         j = seed%4;                 seed /= 4;
5725         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5726         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5727         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5728         j = seed%3 + (seed%3 >= j); seed /= 3;
5729         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5730         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5731         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5732         j = seed%3;                 seed /= 3;
5733         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5734         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5735         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5736         j = seed%2 + (seed%2 >= j); seed /= 2;
5737         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5738         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5739         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5740         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5741         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5742         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5743         put(board, exoPieces[0],    0, 0, ANY);
5744         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5745 }
5746
5747 void
5748 InitPosition (int redraw)
5749 {
5750     ChessSquare (* pieces)[BOARD_FILES];
5751     int i, j, pawnRow, overrule,
5752     oldx = gameInfo.boardWidth,
5753     oldy = gameInfo.boardHeight,
5754     oldh = gameInfo.holdingsWidth;
5755     static int oldv;
5756
5757     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5758
5759     /* [AS] Initialize pv info list [HGM] and game status */
5760     {
5761         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5762             pvInfoList[i].depth = 0;
5763             boards[i][EP_STATUS] = EP_NONE;
5764             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5765         }
5766
5767         initialRulePlies = 0; /* 50-move counter start */
5768
5769         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5770         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5771     }
5772
5773
5774     /* [HGM] logic here is completely changed. In stead of full positions */
5775     /* the initialized data only consist of the two backranks. The switch */
5776     /* selects which one we will use, which is than copied to the Board   */
5777     /* initialPosition, which for the rest is initialized by Pawns and    */
5778     /* empty squares. This initial position is then copied to boards[0],  */
5779     /* possibly after shuffling, so that it remains available.            */
5780
5781     gameInfo.holdingsWidth = 0; /* default board sizes */
5782     gameInfo.boardWidth    = 8;
5783     gameInfo.boardHeight   = 8;
5784     gameInfo.holdingsSize  = 0;
5785     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5786     for(i=0; i<BOARD_FILES-2; i++)
5787       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5788     initialPosition[EP_STATUS] = EP_NONE;
5789     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5790     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5791          SetCharTable(pieceNickName, appData.pieceNickNames);
5792     else SetCharTable(pieceNickName, "............");
5793     pieces = FIDEArray;
5794
5795     switch (gameInfo.variant) {
5796     case VariantFischeRandom:
5797       shuffleOpenings = TRUE;
5798     default:
5799       break;
5800     case VariantShatranj:
5801       pieces = ShatranjArray;
5802       nrCastlingRights = 0;
5803       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5804       break;
5805     case VariantMakruk:
5806       pieces = makrukArray;
5807       nrCastlingRights = 0;
5808       startedFromSetupPosition = TRUE;
5809       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5810       break;
5811     case VariantTwoKings:
5812       pieces = twoKingsArray;
5813       break;
5814     case VariantGrand:
5815       pieces = GrandArray;
5816       nrCastlingRights = 0;
5817       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5818       gameInfo.boardWidth = 10;
5819       gameInfo.boardHeight = 10;
5820       gameInfo.holdingsSize = 7;
5821       break;
5822     case VariantCapaRandom:
5823       shuffleOpenings = TRUE;
5824     case VariantCapablanca:
5825       pieces = CapablancaArray;
5826       gameInfo.boardWidth = 10;
5827       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5828       break;
5829     case VariantGothic:
5830       pieces = GothicArray;
5831       gameInfo.boardWidth = 10;
5832       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5833       break;
5834     case VariantSChess:
5835       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5836       gameInfo.holdingsSize = 7;
5837       break;
5838     case VariantJanus:
5839       pieces = JanusArray;
5840       gameInfo.boardWidth = 10;
5841       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5842       nrCastlingRights = 6;
5843         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5844         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5845         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5846         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5847         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5848         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5849       break;
5850     case VariantFalcon:
5851       pieces = FalconArray;
5852       gameInfo.boardWidth = 10;
5853       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5854       break;
5855     case VariantXiangqi:
5856       pieces = XiangqiArray;
5857       gameInfo.boardWidth  = 9;
5858       gameInfo.boardHeight = 10;
5859       nrCastlingRights = 0;
5860       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5861       break;
5862     case VariantShogi:
5863       pieces = ShogiArray;
5864       gameInfo.boardWidth  = 9;
5865       gameInfo.boardHeight = 9;
5866       gameInfo.holdingsSize = 7;
5867       nrCastlingRights = 0;
5868       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5869       break;
5870     case VariantCourier:
5871       pieces = CourierArray;
5872       gameInfo.boardWidth  = 12;
5873       nrCastlingRights = 0;
5874       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5875       break;
5876     case VariantKnightmate:
5877       pieces = KnightmateArray;
5878       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5879       break;
5880     case VariantSpartan:
5881       pieces = SpartanArray;
5882       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5883       break;
5884     case VariantFairy:
5885       pieces = fairyArray;
5886       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5887       break;
5888     case VariantGreat:
5889       pieces = GreatArray;
5890       gameInfo.boardWidth = 10;
5891       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5892       gameInfo.holdingsSize = 8;
5893       break;
5894     case VariantSuper:
5895       pieces = FIDEArray;
5896       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5897       gameInfo.holdingsSize = 8;
5898       startedFromSetupPosition = TRUE;
5899       break;
5900     case VariantCrazyhouse:
5901     case VariantBughouse:
5902       pieces = FIDEArray;
5903       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5904       gameInfo.holdingsSize = 5;
5905       break;
5906     case VariantWildCastle:
5907       pieces = FIDEArray;
5908       /* !!?shuffle with kings guaranteed to be on d or e file */
5909       shuffleOpenings = 1;
5910       break;
5911     case VariantNoCastle:
5912       pieces = FIDEArray;
5913       nrCastlingRights = 0;
5914       /* !!?unconstrained back-rank shuffle */
5915       shuffleOpenings = 1;
5916       break;
5917     }
5918
5919     overrule = 0;
5920     if(appData.NrFiles >= 0) {
5921         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5922         gameInfo.boardWidth = appData.NrFiles;
5923     }
5924     if(appData.NrRanks >= 0) {
5925         gameInfo.boardHeight = appData.NrRanks;
5926     }
5927     if(appData.holdingsSize >= 0) {
5928         i = appData.holdingsSize;
5929         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5930         gameInfo.holdingsSize = i;
5931     }
5932     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5933     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5934         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5935
5936     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5937     if(pawnRow < 1) pawnRow = 1;
5938     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5939
5940     /* User pieceToChar list overrules defaults */
5941     if(appData.pieceToCharTable != NULL)
5942         SetCharTable(pieceToChar, appData.pieceToCharTable);
5943
5944     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5945
5946         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5947             s = (ChessSquare) 0; /* account holding counts in guard band */
5948         for( i=0; i<BOARD_HEIGHT; i++ )
5949             initialPosition[i][j] = s;
5950
5951         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5952         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5953         initialPosition[pawnRow][j] = WhitePawn;
5954         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5955         if(gameInfo.variant == VariantXiangqi) {
5956             if(j&1) {
5957                 initialPosition[pawnRow][j] =
5958                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5959                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5960                    initialPosition[2][j] = WhiteCannon;
5961                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5962                 }
5963             }
5964         }
5965         if(gameInfo.variant == VariantGrand) {
5966             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5967                initialPosition[0][j] = WhiteRook;
5968                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5969             }
5970         }
5971         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5972     }
5973     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5974
5975             j=BOARD_LEFT+1;
5976             initialPosition[1][j] = WhiteBishop;
5977             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5978             j=BOARD_RGHT-2;
5979             initialPosition[1][j] = WhiteRook;
5980             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5981     }
5982
5983     if( nrCastlingRights == -1) {
5984         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5985         /*       This sets default castling rights from none to normal corners   */
5986         /* Variants with other castling rights must set them themselves above    */
5987         nrCastlingRights = 6;
5988
5989         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5990         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5991         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5992         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5993         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5994         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5995      }
5996
5997      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5998      if(gameInfo.variant == VariantGreat) { // promotion commoners
5999         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6000         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6001         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6002         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6003      }
6004      if( gameInfo.variant == VariantSChess ) {
6005       initialPosition[1][0] = BlackMarshall;
6006       initialPosition[2][0] = BlackAngel;
6007       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6008       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6009       initialPosition[1][1] = initialPosition[2][1] = 
6010       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6011      }
6012   if (appData.debugMode) {
6013     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6014   }
6015     if(shuffleOpenings) {
6016         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6017         startedFromSetupPosition = TRUE;
6018     }
6019     if(startedFromPositionFile) {
6020       /* [HGM] loadPos: use PositionFile for every new game */
6021       CopyBoard(initialPosition, filePosition);
6022       for(i=0; i<nrCastlingRights; i++)
6023           initialRights[i] = filePosition[CASTLING][i];
6024       startedFromSetupPosition = TRUE;
6025     }
6026
6027     CopyBoard(boards[0], initialPosition);
6028
6029     if(oldx != gameInfo.boardWidth ||
6030        oldy != gameInfo.boardHeight ||
6031        oldv != gameInfo.variant ||
6032        oldh != gameInfo.holdingsWidth
6033                                          )
6034             InitDrawingSizes(-2 ,0);
6035
6036     oldv = gameInfo.variant;
6037     if (redraw)
6038       DrawPosition(TRUE, boards[currentMove]);
6039 }
6040
6041 void
6042 SendBoard (ChessProgramState *cps, int moveNum)
6043 {
6044     char message[MSG_SIZ];
6045
6046     if (cps->useSetboard) {
6047       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6048       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6049       SendToProgram(message, cps);
6050       free(fen);
6051
6052     } else {
6053       ChessSquare *bp;
6054       int i, j, left=0, right=BOARD_WIDTH;
6055       /* Kludge to set black to move, avoiding the troublesome and now
6056        * deprecated "black" command.
6057        */
6058       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6059         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6060
6061       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6062
6063       SendToProgram("edit\n", cps);
6064       SendToProgram("#\n", cps);
6065       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6066         bp = &boards[moveNum][i][left];
6067         for (j = left; j < right; j++, bp++) {
6068           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6069           if ((int) *bp < (int) BlackPawn) {
6070             if(j == BOARD_RGHT+1)
6071                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6072             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6073             if(message[0] == '+' || message[0] == '~') {
6074               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6075                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6076                         AAA + j, ONE + i);
6077             }
6078             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6079                 message[1] = BOARD_RGHT   - 1 - j + '1';
6080                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6081             }
6082             SendToProgram(message, cps);
6083           }
6084         }
6085       }
6086
6087       SendToProgram("c\n", cps);
6088       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6089         bp = &boards[moveNum][i][left];
6090         for (j = left; j < right; j++, bp++) {
6091           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6092           if (((int) *bp != (int) EmptySquare)
6093               && ((int) *bp >= (int) BlackPawn)) {
6094             if(j == BOARD_LEFT-2)
6095                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6096             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6097                     AAA + j, ONE + i);
6098             if(message[0] == '+' || message[0] == '~') {
6099               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6100                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6101                         AAA + j, ONE + i);
6102             }
6103             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6104                 message[1] = BOARD_RGHT   - 1 - j + '1';
6105                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6106             }
6107             SendToProgram(message, cps);
6108           }
6109         }
6110       }
6111
6112       SendToProgram(".\n", cps);
6113     }
6114     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6115 }
6116
6117 char exclusionHeader[MSG_SIZ];
6118 int exCnt, excludePtr;
6119 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6120 static Exclusion excluTab[200];
6121 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6122
6123 static void
6124 WriteMap (int s)
6125 {
6126     int j;
6127     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6128     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6129 }
6130
6131 static void
6132 ClearMap ()
6133 {
6134     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6135     excludePtr = 24; exCnt = 0;
6136     WriteMap(0);
6137 }
6138
6139 static void
6140 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6141 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6142     char buf[2*MOVE_LEN], *p;
6143     Exclusion *e = excluTab;
6144     int i;
6145     for(i=0; i<exCnt; i++)
6146         if(e[i].ff == fromX && e[i].fr == fromY &&
6147            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6148     if(i == exCnt) { // was not in exclude list; add it
6149         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6150         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6151             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6152             return; // abort
6153         }
6154         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6155         excludePtr++; e[i].mark = excludePtr++;
6156         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6157         exCnt++;
6158     }
6159     exclusionHeader[e[i].mark] = state;
6160 }
6161
6162 static int
6163 ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, char state)
6164 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6165     char buf[MSG_SIZ];
6166     int j, k;
6167     ChessMove moveType;
6168     if(promoChar == -1) { // kludge to indicate best move
6169         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6170             return 1; // if unparsable, abort
6171     }
6172     // update exclusion map (resolving toggle by consulting existing state)
6173     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6174     j = k%8; k >>= 3;
6175     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6176     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6177          excludeMap[k] |=   1<<j;
6178     else excludeMap[k] &= ~(1<<j);
6179     // update header
6180     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6181     // inform engine
6182     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6183     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6184     SendToProgram(buf, &first);
6185     return (state == '+');
6186 }
6187
6188 static void
6189 ExcludeClick (int index)
6190 {
6191     int i, j;
6192     Exclusion *e = excluTab;
6193     if(index < 25) { // none, best or tail clicked
6194         if(index < 13) { // none: include all
6195             WriteMap(0); // clear map
6196             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6197             SendToProgram("include all\n", &first); // and inform engine
6198         } else if(index > 18) { // tail
6199             if(exclusionHeader[19] == '-') { // tail was excluded
6200                 SendToProgram("include all\n", &first);
6201                 WriteMap(0); // clear map completely
6202                 // now re-exclude selected moves
6203                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6204                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6205             } else { // tail was included or in mixed state
6206                 SendToProgram("exclude all\n", &first);
6207                 WriteMap(0xFF); // fill map completely
6208                 // now re-include selected moves
6209                 j = 0; // count them
6210                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6211                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6212                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6213             }
6214         } else { // best
6215             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6216         }
6217     } else {
6218         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6219             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6220             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6221             break;
6222         }
6223     }
6224 }
6225
6226 ChessSquare
6227 DefaultPromoChoice (int white)
6228 {
6229     ChessSquare result;
6230     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6231         result = WhiteFerz; // no choice
6232     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6233         result= WhiteKing; // in Suicide Q is the last thing we want
6234     else if(gameInfo.variant == VariantSpartan)
6235         result = white ? WhiteQueen : WhiteAngel;
6236     else result = WhiteQueen;
6237     if(!white) result = WHITE_TO_BLACK result;
6238     return result;
6239 }
6240
6241 static int autoQueen; // [HGM] oneclick
6242
6243 int
6244 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6245 {
6246     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6247     /* [HGM] add Shogi promotions */
6248     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6249     ChessSquare piece;
6250     ChessMove moveType;
6251     Boolean premove;
6252
6253     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6254     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6255
6256     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6257       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6258         return FALSE;
6259
6260     piece = boards[currentMove][fromY][fromX];
6261     if(gameInfo.variant == VariantShogi) {
6262         promotionZoneSize = BOARD_HEIGHT/3;
6263         highestPromotingPiece = (int)WhiteFerz;
6264     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6265         promotionZoneSize = 3;
6266     }
6267
6268     // Treat Lance as Pawn when it is not representing Amazon
6269     if(gameInfo.variant != VariantSuper) {
6270         if(piece == WhiteLance) piece = WhitePawn; else
6271         if(piece == BlackLance) piece = BlackPawn;
6272     }
6273
6274     // next weed out all moves that do not touch the promotion zone at all
6275     if((int)piece >= BlackPawn) {
6276         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6277              return FALSE;
6278         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6279     } else {
6280         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6281            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6282     }
6283
6284     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6285
6286     // weed out mandatory Shogi promotions
6287     if(gameInfo.variant == VariantShogi) {
6288         if(piece >= BlackPawn) {
6289             if(toY == 0 && piece == BlackPawn ||
6290                toY == 0 && piece == BlackQueen ||
6291                toY <= 1 && piece == BlackKnight) {
6292                 *promoChoice = '+';
6293                 return FALSE;
6294             }
6295         } else {
6296             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6297                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6298                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6299                 *promoChoice = '+';
6300                 return FALSE;
6301             }
6302         }
6303     }
6304
6305     // weed out obviously illegal Pawn moves
6306     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6307         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6308         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6309         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6310         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6311         // note we are not allowed to test for valid (non-)capture, due to premove
6312     }
6313
6314     // we either have a choice what to promote to, or (in Shogi) whether to promote
6315     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6316         *promoChoice = PieceToChar(BlackFerz);  // no choice
6317         return FALSE;
6318     }
6319     // no sense asking what we must promote to if it is going to explode...
6320     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6321         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6322         return FALSE;
6323     }
6324     // give caller the default choice even if we will not make it
6325     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6326     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6327     if(        sweepSelect && gameInfo.variant != VariantGreat
6328                            && gameInfo.variant != VariantGrand
6329                            && gameInfo.variant != VariantSuper) return FALSE;
6330     if(autoQueen) return FALSE; // predetermined
6331
6332     // suppress promotion popup on illegal moves that are not premoves
6333     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6334               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6335     if(appData.testLegality && !premove) {
6336         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6337                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6338         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6339             return FALSE;
6340     }
6341
6342     return TRUE;
6343 }
6344
6345 int
6346 InPalace (int row, int column)
6347 {   /* [HGM] for Xiangqi */
6348     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6349          column < (BOARD_WIDTH + 4)/2 &&
6350          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6351     return FALSE;
6352 }
6353
6354 int
6355 PieceForSquare (int x, int y)
6356 {
6357   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6358      return -1;
6359   else
6360      return boards[currentMove][y][x];
6361 }
6362
6363 int
6364 OKToStartUserMove (int x, int y)
6365 {
6366     ChessSquare from_piece;
6367     int white_piece;
6368
6369     if (matchMode) return FALSE;
6370     if (gameMode == EditPosition) return TRUE;
6371
6372     if (x >= 0 && y >= 0)
6373       from_piece = boards[currentMove][y][x];
6374     else
6375       from_piece = EmptySquare;
6376
6377     if (from_piece == EmptySquare) return FALSE;
6378
6379     white_piece = (int)from_piece >= (int)WhitePawn &&
6380       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6381
6382     switch (gameMode) {
6383       case AnalyzeFile:
6384       case TwoMachinesPlay:
6385       case EndOfGame:
6386         return FALSE;
6387
6388       case IcsObserving:
6389       case IcsIdle:
6390         return FALSE;
6391
6392       case MachinePlaysWhite:
6393       case IcsPlayingBlack:
6394         if (appData.zippyPlay) return FALSE;
6395         if (white_piece) {
6396             DisplayMoveError(_("You are playing Black"));
6397             return FALSE;
6398         }
6399         break;
6400
6401       case MachinePlaysBlack:
6402       case IcsPlayingWhite:
6403         if (appData.zippyPlay) return FALSE;
6404         if (!white_piece) {
6405             DisplayMoveError(_("You are playing White"));
6406             return FALSE;
6407         }
6408         break;
6409
6410       case PlayFromGameFile:
6411             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6412       case EditGame:
6413         if (!white_piece && WhiteOnMove(currentMove)) {
6414             DisplayMoveError(_("It is White's turn"));
6415             return FALSE;
6416         }
6417         if (white_piece && !WhiteOnMove(currentMove)) {
6418             DisplayMoveError(_("It is Black's turn"));
6419             return FALSE;
6420         }
6421         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6422             /* Editing correspondence game history */
6423             /* Could disallow this or prompt for confirmation */
6424             cmailOldMove = -1;
6425         }
6426         break;
6427
6428       case BeginningOfGame:
6429         if (appData.icsActive) return FALSE;
6430         if (!appData.noChessProgram) {
6431             if (!white_piece) {
6432                 DisplayMoveError(_("You are playing White"));
6433                 return FALSE;
6434             }
6435         }
6436         break;
6437
6438       case Training:
6439         if (!white_piece && WhiteOnMove(currentMove)) {
6440             DisplayMoveError(_("It is White's turn"));
6441             return FALSE;
6442         }
6443         if (white_piece && !WhiteOnMove(currentMove)) {
6444             DisplayMoveError(_("It is Black's turn"));
6445             return FALSE;
6446         }
6447         break;
6448
6449       default:
6450       case IcsExamining:
6451         break;
6452     }
6453     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6454         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6455         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6456         && gameMode != AnalyzeFile && gameMode != Training) {
6457         DisplayMoveError(_("Displayed position is not current"));
6458         return FALSE;
6459     }
6460     return TRUE;
6461 }
6462
6463 Boolean
6464 OnlyMove (int *x, int *y, Boolean captures) 
6465 {
6466     DisambiguateClosure cl;
6467     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6468     switch(gameMode) {
6469       case MachinePlaysBlack:
6470       case IcsPlayingWhite:
6471       case BeginningOfGame:
6472         if(!WhiteOnMove(currentMove)) return FALSE;
6473         break;
6474       case MachinePlaysWhite:
6475       case IcsPlayingBlack:
6476         if(WhiteOnMove(currentMove)) return FALSE;
6477         break;
6478       case EditGame:
6479         break;
6480       default:
6481         return FALSE;
6482     }
6483     cl.pieceIn = EmptySquare;
6484     cl.rfIn = *y;
6485     cl.ffIn = *x;
6486     cl.rtIn = -1;
6487     cl.ftIn = -1;
6488     cl.promoCharIn = NULLCHAR;
6489     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6490     if( cl.kind == NormalMove ||
6491         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6492         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6493         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6494       fromX = cl.ff;
6495       fromY = cl.rf;
6496       *x = cl.ft;
6497       *y = cl.rt;
6498       return TRUE;
6499     }
6500     if(cl.kind != ImpossibleMove) return FALSE;
6501     cl.pieceIn = EmptySquare;
6502     cl.rfIn = -1;
6503     cl.ffIn = -1;
6504     cl.rtIn = *y;
6505     cl.ftIn = *x;
6506     cl.promoCharIn = NULLCHAR;
6507     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6508     if( cl.kind == NormalMove ||
6509         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6510         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6511         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6512       fromX = cl.ff;
6513       fromY = cl.rf;
6514       *x = cl.ft;
6515       *y = cl.rt;
6516       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6517       return TRUE;
6518     }
6519     return FALSE;
6520 }
6521
6522 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6523 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6524 int lastLoadGameUseList = FALSE;
6525 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6526 ChessMove lastLoadGameStart = EndOfFile;
6527 int doubleClick;
6528
6529 void
6530 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6531 {
6532     ChessMove moveType;
6533     ChessSquare pdown, pup;
6534     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6535
6536
6537     /* Check if the user is playing in turn.  This is complicated because we
6538        let the user "pick up" a piece before it is his turn.  So the piece he
6539        tried to pick up may have been captured by the time he puts it down!
6540        Therefore we use the color the user is supposed to be playing in this
6541        test, not the color of the piece that is currently on the starting
6542        square---except in EditGame mode, where the user is playing both
6543        sides; fortunately there the capture race can't happen.  (It can
6544        now happen in IcsExamining mode, but that's just too bad.  The user
6545        will get a somewhat confusing message in that case.)
6546        */
6547
6548     switch (gameMode) {
6549       case AnalyzeFile:
6550       case TwoMachinesPlay:
6551       case EndOfGame:
6552       case IcsObserving:
6553       case IcsIdle:
6554         /* We switched into a game mode where moves are not accepted,
6555            perhaps while the mouse button was down. */
6556         return;
6557
6558       case MachinePlaysWhite:
6559         /* User is moving for Black */
6560         if (WhiteOnMove(currentMove)) {
6561             DisplayMoveError(_("It is White's turn"));
6562             return;
6563         }
6564         break;
6565
6566       case MachinePlaysBlack:
6567         /* User is moving for White */
6568         if (!WhiteOnMove(currentMove)) {
6569             DisplayMoveError(_("It is Black's turn"));
6570             return;
6571         }
6572         break;
6573
6574       case PlayFromGameFile:
6575             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6576       case EditGame:
6577       case IcsExamining:
6578       case BeginningOfGame:
6579       case AnalyzeMode:
6580       case Training:
6581         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6582         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6583             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6584             /* User is moving for Black */
6585             if (WhiteOnMove(currentMove)) {
6586                 DisplayMoveError(_("It is White's turn"));
6587                 return;
6588             }
6589         } else {
6590             /* User is moving for White */
6591             if (!WhiteOnMove(currentMove)) {
6592                 DisplayMoveError(_("It is Black's turn"));
6593                 return;
6594             }
6595         }
6596         break;
6597
6598       case IcsPlayingBlack:
6599         /* User is moving for Black */
6600         if (WhiteOnMove(currentMove)) {
6601             if (!appData.premove) {
6602                 DisplayMoveError(_("It is White's turn"));
6603             } else if (toX >= 0 && toY >= 0) {
6604                 premoveToX = toX;
6605                 premoveToY = toY;
6606                 premoveFromX = fromX;
6607                 premoveFromY = fromY;
6608                 premovePromoChar = promoChar;
6609                 gotPremove = 1;
6610                 if (appData.debugMode)
6611                     fprintf(debugFP, "Got premove: fromX %d,"
6612                             "fromY %d, toX %d, toY %d\n",
6613                             fromX, fromY, toX, toY);
6614             }
6615             return;
6616         }
6617         break;
6618
6619       case IcsPlayingWhite:
6620         /* User is moving for White */
6621         if (!WhiteOnMove(currentMove)) {
6622             if (!appData.premove) {
6623                 DisplayMoveError(_("It is Black's turn"));
6624             } else if (toX >= 0 && toY >= 0) {
6625                 premoveToX = toX;
6626                 premoveToY = toY;
6627                 premoveFromX = fromX;
6628                 premoveFromY = fromY;
6629                 premovePromoChar = promoChar;
6630                 gotPremove = 1;
6631                 if (appData.debugMode)
6632                     fprintf(debugFP, "Got premove: fromX %d,"
6633                             "fromY %d, toX %d, toY %d\n",
6634                             fromX, fromY, toX, toY);
6635             }
6636             return;
6637         }
6638         break;
6639
6640       default:
6641         break;
6642
6643       case EditPosition:
6644         /* EditPosition, empty square, or different color piece;
6645            click-click move is possible */
6646         if (toX == -2 || toY == -2) {
6647             boards[0][fromY][fromX] = EmptySquare;
6648             DrawPosition(FALSE, boards[currentMove]);
6649             return;
6650         } else if (toX >= 0 && toY >= 0) {
6651             boards[0][toY][toX] = boards[0][fromY][fromX];
6652             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6653                 if(boards[0][fromY][0] != EmptySquare) {
6654                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6655                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6656                 }
6657             } else
6658             if(fromX == BOARD_RGHT+1) {
6659                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6660                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6661                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6662                 }
6663             } else
6664             boards[0][fromY][fromX] = EmptySquare;
6665             DrawPosition(FALSE, boards[currentMove]);
6666             return;
6667         }
6668         return;
6669     }
6670
6671     if(toX < 0 || toY < 0) return;
6672     pdown = boards[currentMove][fromY][fromX];
6673     pup = boards[currentMove][toY][toX];
6674
6675     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6676     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6677          if( pup != EmptySquare ) return;
6678          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6679            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6680                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6681            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6682            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6683            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6684            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6685          fromY = DROP_RANK;
6686     }
6687
6688     /* [HGM] always test for legality, to get promotion info */
6689     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6690                                          fromY, fromX, toY, toX, promoChar);
6691
6692     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6693
6694     /* [HGM] but possibly ignore an IllegalMove result */
6695     if (appData.testLegality) {
6696         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6697             DisplayMoveError(_("Illegal move"));
6698             return;
6699         }
6700     }
6701
6702     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6703         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6704              ClearPremoveHighlights(); // was included
6705         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6706         return;
6707     }
6708
6709     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6710 }
6711
6712 /* Common tail of UserMoveEvent and DropMenuEvent */
6713 int
6714 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6715 {
6716     char *bookHit = 0;
6717
6718     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6719         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6720         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6721         if(WhiteOnMove(currentMove)) {
6722             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6723         } else {
6724             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6725         }
6726     }
6727
6728     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6729        move type in caller when we know the move is a legal promotion */
6730     if(moveType == NormalMove && promoChar)
6731         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6732
6733     /* [HGM] <popupFix> The following if has been moved here from
6734        UserMoveEvent(). Because it seemed to belong here (why not allow
6735        piece drops in training games?), and because it can only be
6736        performed after it is known to what we promote. */
6737     if (gameMode == Training) {
6738       /* compare the move played on the board to the next move in the
6739        * game. If they match, display the move and the opponent's response.
6740        * If they don't match, display an error message.
6741        */
6742       int saveAnimate;
6743       Board testBoard;
6744       CopyBoard(testBoard, boards[currentMove]);
6745       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6746
6747       if (CompareBoards(testBoard, boards[currentMove+1])) {
6748         ForwardInner(currentMove+1);
6749
6750         /* Autoplay the opponent's response.
6751          * if appData.animate was TRUE when Training mode was entered,
6752          * the response will be animated.
6753          */
6754         saveAnimate = appData.animate;
6755         appData.animate = animateTraining;
6756         ForwardInner(currentMove+1);
6757         appData.animate = saveAnimate;
6758
6759         /* check for the end of the game */
6760         if (currentMove >= forwardMostMove) {
6761           gameMode = PlayFromGameFile;
6762           ModeHighlight();
6763           SetTrainingModeOff();
6764           DisplayInformation(_("End of game"));
6765         }
6766       } else {
6767         DisplayError(_("Incorrect move"), 0);
6768       }
6769       return 1;
6770     }
6771
6772   /* Ok, now we know that the move is good, so we can kill
6773      the previous line in Analysis Mode */
6774   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6775                                 && currentMove < forwardMostMove) {
6776     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6777     else forwardMostMove = currentMove;
6778   }
6779
6780   ClearMap();
6781
6782   /* If we need the chess program but it's dead, restart it */
6783   ResurrectChessProgram();
6784
6785   /* A user move restarts a paused game*/
6786   if (pausing)
6787     PauseEvent();
6788
6789   thinkOutput[0] = NULLCHAR;
6790
6791   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6792
6793   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6794     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6795     return 1;
6796   }
6797
6798   if (gameMode == BeginningOfGame) {
6799     if (appData.noChessProgram) {
6800       gameMode = EditGame;
6801       SetGameInfo();
6802     } else {
6803       char buf[MSG_SIZ];
6804       gameMode = MachinePlaysBlack;
6805       StartClocks();
6806       SetGameInfo();
6807       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6808       DisplayTitle(buf);
6809       if (first.sendName) {
6810         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6811         SendToProgram(buf, &first);
6812       }
6813       StartClocks();
6814     }
6815     ModeHighlight();
6816   }
6817
6818   /* Relay move to ICS or chess engine */
6819   if (appData.icsActive) {
6820     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6821         gameMode == IcsExamining) {
6822       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6823         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6824         SendToICS("draw ");
6825         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6826       }
6827       // also send plain move, in case ICS does not understand atomic claims
6828       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6829       ics_user_moved = 1;
6830     }
6831   } else {
6832     if (first.sendTime && (gameMode == BeginningOfGame ||
6833                            gameMode == MachinePlaysWhite ||
6834                            gameMode == MachinePlaysBlack)) {
6835       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6836     }
6837     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6838          // [HGM] book: if program might be playing, let it use book
6839         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6840         first.maybeThinking = TRUE;
6841     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6842         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6843         SendBoard(&first, currentMove+1);
6844     } else SendMoveToProgram(forwardMostMove-1, &first);
6845     if (currentMove == cmailOldMove + 1) {
6846       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6847     }
6848   }
6849
6850   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6851
6852   switch (gameMode) {
6853   case EditGame:
6854     if(appData.testLegality)
6855     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6856     case MT_NONE:
6857     case MT_CHECK:
6858       break;
6859     case MT_CHECKMATE:
6860     case MT_STAINMATE:
6861       if (WhiteOnMove(currentMove)) {
6862         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6863       } else {
6864         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6865       }
6866       break;
6867     case MT_STALEMATE:
6868       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6869       break;
6870     }
6871     break;
6872
6873   case MachinePlaysBlack:
6874   case MachinePlaysWhite:
6875     /* disable certain menu options while machine is thinking */
6876     SetMachineThinkingEnables();
6877     break;
6878
6879   default:
6880     break;
6881   }
6882
6883   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6884   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6885
6886   if(bookHit) { // [HGM] book: simulate book reply
6887         static char bookMove[MSG_SIZ]; // a bit generous?
6888
6889         programStats.nodes = programStats.depth = programStats.time =
6890         programStats.score = programStats.got_only_move = 0;
6891         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6892
6893         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6894         strcat(bookMove, bookHit);
6895         HandleMachineMove(bookMove, &first);
6896   }
6897   return 1;
6898 }
6899
6900 void
6901 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6902 {
6903     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6904     Markers *m = (Markers *) closure;
6905     if(rf == fromY && ff == fromX)
6906         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6907                          || kind == WhiteCapturesEnPassant
6908                          || kind == BlackCapturesEnPassant);
6909     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6910 }
6911
6912 void
6913 MarkTargetSquares (int clear)
6914 {
6915   int x, y;
6916   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6917      !appData.testLegality || gameMode == EditPosition) return;
6918   if(clear) {
6919     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6920   } else {
6921     int capt = 0;
6922     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6923     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6924       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6925       if(capt)
6926       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6927     }
6928   }
6929   DrawPosition(TRUE, NULL);
6930 }
6931
6932 int
6933 Explode (Board board, int fromX, int fromY, int toX, int toY)
6934 {
6935     if(gameInfo.variant == VariantAtomic &&
6936        (board[toY][toX] != EmptySquare ||                     // capture?
6937         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6938                          board[fromY][fromX] == BlackPawn   )
6939       )) {
6940         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6941         return TRUE;
6942     }
6943     return FALSE;
6944 }
6945
6946 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6947
6948 int
6949 CanPromote (ChessSquare piece, int y)
6950 {
6951         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6952         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6953         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6954            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6955            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6956                                                   gameInfo.variant == VariantMakruk) return FALSE;
6957         return (piece == BlackPawn && y == 1 ||
6958                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6959                 piece == BlackLance && y == 1 ||
6960                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6961 }
6962
6963 void
6964 LeftClick (ClickType clickType, int xPix, int yPix)
6965 {
6966     int x, y;
6967     Boolean saveAnimate;
6968     static int second = 0, promotionChoice = 0, clearFlag = 0;
6969     char promoChoice = NULLCHAR;
6970     ChessSquare piece;
6971     static TimeMark lastClickTime, prevClickTime;
6972
6973     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
6974
6975     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
6976
6977     if (clickType == Press) ErrorPopDown();
6978
6979     x = EventToSquare(xPix, BOARD_WIDTH);
6980     y = EventToSquare(yPix, BOARD_HEIGHT);
6981     if (!flipView && y >= 0) {
6982         y = BOARD_HEIGHT - 1 - y;
6983     }
6984     if (flipView && x >= 0) {
6985         x = BOARD_WIDTH - 1 - x;
6986     }
6987
6988     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6989         defaultPromoChoice = promoSweep;
6990         promoSweep = EmptySquare;   // terminate sweep
6991         promoDefaultAltered = TRUE;
6992         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6993     }
6994
6995     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6996         if(clickType == Release) return; // ignore upclick of click-click destination
6997         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6998         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6999         if(gameInfo.holdingsWidth &&
7000                 (WhiteOnMove(currentMove)
7001                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7002                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7003             // click in right holdings, for determining promotion piece
7004             ChessSquare p = boards[currentMove][y][x];
7005             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7006             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7007             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7008                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7009                 fromX = fromY = -1;
7010                 return;
7011             }
7012         }
7013         DrawPosition(FALSE, boards[currentMove]);
7014         return;
7015     }
7016
7017     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7018     if(clickType == Press
7019             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7020               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7021               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7022         return;
7023
7024     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7025         // could be static click on premove from-square: abort premove
7026         gotPremove = 0;
7027         ClearPremoveHighlights();
7028     }
7029
7030     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7031         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7032
7033     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7034         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7035                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7036         defaultPromoChoice = DefaultPromoChoice(side);
7037     }
7038
7039     autoQueen = appData.alwaysPromoteToQueen;
7040
7041     if (fromX == -1) {
7042       int originalY = y;
7043       gatingPiece = EmptySquare;
7044       if (clickType != Press) {
7045         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7046             DragPieceEnd(xPix, yPix); dragging = 0;
7047             DrawPosition(FALSE, NULL);
7048         }
7049         return;
7050       }
7051       doubleClick = FALSE;
7052       fromX = x; fromY = y; toX = toY = -1;
7053       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7054          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7055          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7056             /* First square */
7057             if (OKToStartUserMove(fromX, fromY)) {
7058                 second = 0;
7059                 MarkTargetSquares(0);
7060                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7061                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7062                     promoSweep = defaultPromoChoice;
7063                     selectFlag = 0; lastX = xPix; lastY = yPix;
7064                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7065                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7066                 }
7067                 if (appData.highlightDragging) {
7068                     SetHighlights(fromX, fromY, -1, -1);
7069                 }
7070             } else fromX = fromY = -1;
7071             return;
7072         }
7073     }
7074
7075     /* fromX != -1 */
7076     if (clickType == Press && gameMode != EditPosition) {
7077         ChessSquare fromP;
7078         ChessSquare toP;
7079         int frc;
7080
7081         // ignore off-board to clicks
7082         if(y < 0 || x < 0) return;
7083
7084         /* Check if clicking again on the same color piece */
7085         fromP = boards[currentMove][fromY][fromX];
7086         toP = boards[currentMove][y][x];
7087         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7088         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7089              WhitePawn <= toP && toP <= WhiteKing &&
7090              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7091              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7092             (BlackPawn <= fromP && fromP <= BlackKing &&
7093              BlackPawn <= toP && toP <= BlackKing &&
7094              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7095              !(fromP == BlackKing && toP == BlackRook && frc))) {
7096             /* Clicked again on same color piece -- changed his mind */
7097             second = (x == fromX && y == fromY);
7098             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7099                 second = FALSE; // first double-click rather than scond click
7100                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7101             }
7102             promoDefaultAltered = FALSE;
7103             MarkTargetSquares(1);
7104            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7105             if (appData.highlightDragging) {
7106                 SetHighlights(x, y, -1, -1);
7107             } else {
7108                 ClearHighlights();
7109             }
7110             if (OKToStartUserMove(x, y)) {
7111                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7112                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7113                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7114                  gatingPiece = boards[currentMove][fromY][fromX];
7115                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7116                 fromX = x;
7117                 fromY = y; dragging = 1;
7118                 MarkTargetSquares(0);
7119                 DragPieceBegin(xPix, yPix, FALSE);
7120                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7121                     promoSweep = defaultPromoChoice;
7122                     selectFlag = 0; lastX = xPix; lastY = yPix;
7123                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7124                 }
7125             }
7126            }
7127            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7128            second = FALSE; 
7129         }
7130         // ignore clicks on holdings
7131         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7132     }
7133
7134     if (clickType == Release && x == fromX && y == fromY) {
7135         DragPieceEnd(xPix, yPix); dragging = 0;
7136         if(clearFlag) {
7137             // a deferred attempt to click-click move an empty square on top of a piece
7138             boards[currentMove][y][x] = EmptySquare;
7139             ClearHighlights();
7140             DrawPosition(FALSE, boards[currentMove]);
7141             fromX = fromY = -1; clearFlag = 0;
7142             return;
7143         }
7144         if (appData.animateDragging) {
7145             /* Undo animation damage if any */
7146             DrawPosition(FALSE, NULL);
7147         }
7148         if (second) {
7149             /* Second up/down in same square; just abort move */
7150             second = 0;
7151             fromX = fromY = -1;
7152             gatingPiece = EmptySquare;
7153             ClearHighlights();
7154             gotPremove = 0;
7155             ClearPremoveHighlights();
7156         } else {
7157             /* First upclick in same square; start click-click mode */
7158             SetHighlights(x, y, -1, -1);
7159         }
7160         return;
7161     }
7162
7163     clearFlag = 0;
7164
7165     /* we now have a different from- and (possibly off-board) to-square */
7166     /* Completed move */
7167     toX = x;
7168     toY = y;
7169     saveAnimate = appData.animate;
7170     MarkTargetSquares(1);
7171     if (clickType == Press) {
7172         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7173             // must be Edit Position mode with empty-square selected
7174             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7175             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7176             return;
7177         }
7178         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7179             ChessSquare piece = boards[currentMove][fromY][fromX];
7180             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7181             promoSweep = defaultPromoChoice;
7182             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7183             selectFlag = 0; lastX = xPix; lastY = yPix;
7184             Sweep(0); // Pawn that is going to promote: preview promotion piece
7185             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7186             DrawPosition(FALSE, boards[currentMove]);
7187             return;
7188         }
7189         /* Finish clickclick move */
7190         if (appData.animate || appData.highlightLastMove) {
7191             SetHighlights(fromX, fromY, toX, toY);
7192         } else {
7193             ClearHighlights();
7194         }
7195     } else {
7196         /* Finish drag move */
7197         if (appData.highlightLastMove) {
7198             SetHighlights(fromX, fromY, toX, toY);
7199         } else {
7200             ClearHighlights();
7201         }
7202         DragPieceEnd(xPix, yPix); dragging = 0;
7203         /* Don't animate move and drag both */
7204         appData.animate = FALSE;
7205     }
7206
7207     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7208     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7209         ChessSquare piece = boards[currentMove][fromY][fromX];
7210         if(gameMode == EditPosition && piece != EmptySquare &&
7211            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7212             int n;
7213
7214             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7215                 n = PieceToNumber(piece - (int)BlackPawn);
7216                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7217                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7218                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7219             } else
7220             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7221                 n = PieceToNumber(piece);
7222                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7223                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7224                 boards[currentMove][n][BOARD_WIDTH-2]++;
7225             }
7226             boards[currentMove][fromY][fromX] = EmptySquare;
7227         }
7228         ClearHighlights();
7229         fromX = fromY = -1;
7230         DrawPosition(TRUE, boards[currentMove]);
7231         return;
7232     }
7233
7234     // off-board moves should not be highlighted
7235     if(x < 0 || y < 0) ClearHighlights();
7236
7237     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7238
7239     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7240         SetHighlights(fromX, fromY, toX, toY);
7241         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7242             // [HGM] super: promotion to captured piece selected from holdings
7243             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7244             promotionChoice = TRUE;
7245             // kludge follows to temporarily execute move on display, without promoting yet
7246             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7247             boards[currentMove][toY][toX] = p;
7248             DrawPosition(FALSE, boards[currentMove]);
7249             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7250             boards[currentMove][toY][toX] = q;
7251             DisplayMessage("Click in holdings to choose piece", "");
7252             return;
7253         }
7254         PromotionPopUp();
7255     } else {
7256         int oldMove = currentMove;
7257         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7258         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7259         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7260         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7261            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7262             DrawPosition(TRUE, boards[currentMove]);
7263         fromX = fromY = -1;
7264     }
7265     appData.animate = saveAnimate;
7266     if (appData.animate || appData.animateDragging) {
7267         /* Undo animation damage if needed */
7268         DrawPosition(FALSE, NULL);
7269     }
7270 }
7271
7272 int
7273 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7274 {   // front-end-free part taken out of PieceMenuPopup
7275     int whichMenu; int xSqr, ySqr;
7276
7277     if(seekGraphUp) { // [HGM] seekgraph
7278         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7279         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7280         return -2;
7281     }
7282
7283     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7284          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7285         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7286         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7287         if(action == Press)   {
7288             originalFlip = flipView;
7289             flipView = !flipView; // temporarily flip board to see game from partners perspective
7290             DrawPosition(TRUE, partnerBoard);
7291             DisplayMessage(partnerStatus, "");
7292             partnerUp = TRUE;
7293         } else if(action == Release) {
7294             flipView = originalFlip;
7295             DrawPosition(TRUE, boards[currentMove]);
7296             partnerUp = FALSE;
7297         }
7298         return -2;
7299     }
7300
7301     xSqr = EventToSquare(x, BOARD_WIDTH);
7302     ySqr = EventToSquare(y, BOARD_HEIGHT);
7303     if (action == Release) {
7304         if(pieceSweep != EmptySquare) {
7305             EditPositionMenuEvent(pieceSweep, toX, toY);
7306             pieceSweep = EmptySquare;
7307         } else UnLoadPV(); // [HGM] pv
7308     }
7309     if (action != Press) return -2; // return code to be ignored
7310     switch (gameMode) {
7311       case IcsExamining:
7312         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7313       case EditPosition:
7314         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7315         if (xSqr < 0 || ySqr < 0) return -1;
7316         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7317         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7318         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7319         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7320         NextPiece(0);
7321         return 2; // grab
7322       case IcsObserving:
7323         if(!appData.icsEngineAnalyze) return -1;
7324       case IcsPlayingWhite:
7325       case IcsPlayingBlack:
7326         if(!appData.zippyPlay) goto noZip;
7327       case AnalyzeMode:
7328       case AnalyzeFile:
7329       case MachinePlaysWhite:
7330       case MachinePlaysBlack:
7331       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7332         if (!appData.dropMenu) {
7333           LoadPV(x, y);
7334           return 2; // flag front-end to grab mouse events
7335         }
7336         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7337            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7338       case EditGame:
7339       noZip:
7340         if (xSqr < 0 || ySqr < 0) return -1;
7341         if (!appData.dropMenu || appData.testLegality &&
7342             gameInfo.variant != VariantBughouse &&
7343             gameInfo.variant != VariantCrazyhouse) return -1;
7344         whichMenu = 1; // drop menu
7345         break;
7346       default:
7347         return -1;
7348     }
7349
7350     if (((*fromX = xSqr) < 0) ||
7351         ((*fromY = ySqr) < 0)) {
7352         *fromX = *fromY = -1;
7353         return -1;
7354     }
7355     if (flipView)
7356       *fromX = BOARD_WIDTH - 1 - *fromX;
7357     else
7358       *fromY = BOARD_HEIGHT - 1 - *fromY;
7359
7360     return whichMenu;
7361 }
7362
7363 void
7364 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7365 {
7366 //    char * hint = lastHint;
7367     FrontEndProgramStats stats;
7368
7369     stats.which = cps == &first ? 0 : 1;
7370     stats.depth = cpstats->depth;
7371     stats.nodes = cpstats->nodes;
7372     stats.score = cpstats->score;
7373     stats.time = cpstats->time;
7374     stats.pv = cpstats->movelist;
7375     stats.hint = lastHint;
7376     stats.an_move_index = 0;
7377     stats.an_move_count = 0;
7378
7379     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7380         stats.hint = cpstats->move_name;
7381         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7382         stats.an_move_count = cpstats->nr_moves;
7383     }
7384
7385     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
7386
7387     SetProgramStats( &stats );
7388 }
7389
7390 void
7391 ClearEngineOutputPane (int which)
7392 {
7393     static FrontEndProgramStats dummyStats;
7394     dummyStats.which = which;
7395     dummyStats.pv = "#";
7396     SetProgramStats( &dummyStats );
7397 }
7398
7399 #define MAXPLAYERS 500
7400
7401 char *
7402 TourneyStandings (int display)
7403 {
7404     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7405     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7406     char result, *p, *names[MAXPLAYERS];
7407
7408     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7409         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7410     names[0] = p = strdup(appData.participants);
7411     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7412
7413     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7414
7415     while(result = appData.results[nr]) {
7416         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7417         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7418         wScore = bScore = 0;
7419         switch(result) {
7420           case '+': wScore = 2; break;
7421           case '-': bScore = 2; break;
7422           case '=': wScore = bScore = 1; break;
7423           case ' ':
7424           case '*': return strdup("busy"); // tourney not finished
7425         }
7426         score[w] += wScore;
7427         score[b] += bScore;
7428         games[w]++;
7429         games[b]++;
7430         nr++;
7431     }
7432     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7433     for(w=0; w<nPlayers; w++) {
7434         bScore = -1;
7435         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7436         ranking[w] = b; points[w] = bScore; score[b] = -2;
7437     }
7438     p = malloc(nPlayers*34+1);
7439     for(w=0; w<nPlayers && w<display; w++)
7440         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7441     free(names[0]);
7442     return p;
7443 }
7444
7445 void
7446 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7447 {       // count all piece types
7448         int p, f, r;
7449         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7450         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7451         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7452                 p = board[r][f];
7453                 pCnt[p]++;
7454                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7455                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7456                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7457                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7458                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7459                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7460         }
7461 }
7462
7463 int
7464 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7465 {
7466         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7467         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7468
7469         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7470         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7471         if(myPawns == 2 && nMine == 3) // KPP
7472             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7473         if(myPawns == 1 && nMine == 2) // KP
7474             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7475         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7476             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7477         if(myPawns) return FALSE;
7478         if(pCnt[WhiteRook+side])
7479             return pCnt[BlackRook-side] ||
7480                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7481                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7482                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7483         if(pCnt[WhiteCannon+side]) {
7484             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7485             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7486         }
7487         if(pCnt[WhiteKnight+side])
7488             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7489         return FALSE;
7490 }
7491
7492 int
7493 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7494 {
7495         VariantClass v = gameInfo.variant;
7496
7497         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7498         if(v == VariantShatranj) return TRUE; // always winnable through baring
7499         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7500         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7501
7502         if(v == VariantXiangqi) {
7503                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7504
7505                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7506                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7507                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7508                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7509                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7510                 if(stale) // we have at least one last-rank P plus perhaps C
7511                     return majors // KPKX
7512                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7513                 else // KCA*E*
7514                     return pCnt[WhiteFerz+side] // KCAK
7515                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7516                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7517                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7518
7519         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7520                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7521
7522                 if(nMine == 1) return FALSE; // bare King
7523                 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
7524                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7525                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7526                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7527                 if(pCnt[WhiteKnight+side])
7528                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7529                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7530                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7531                 if(nBishops)
7532                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7533                 if(pCnt[WhiteAlfil+side])
7534                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7535                 if(pCnt[WhiteWazir+side])
7536                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7537         }
7538
7539         return TRUE;
7540 }
7541
7542 int
7543 CompareWithRights (Board b1, Board b2)
7544 {
7545     int rights = 0;
7546     if(!CompareBoards(b1, b2)) return FALSE;
7547     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7548     /* compare castling rights */
7549     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7550            rights++; /* King lost rights, while rook still had them */
7551     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7552         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7553            rights++; /* but at least one rook lost them */
7554     }
7555     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7556            rights++;
7557     if( b1[CASTLING][5] != NoRights ) {
7558         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7559            rights++;
7560     }
7561     return rights == 0;
7562 }
7563
7564 int
7565 Adjudicate (ChessProgramState *cps)
7566 {       // [HGM] some adjudications useful with buggy engines
7567         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7568         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7569         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7570         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7571         int k, count = 0; static int bare = 1;
7572         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7573         Boolean canAdjudicate = !appData.icsActive;
7574
7575         // most tests only when we understand the game, i.e. legality-checking on
7576             if( appData.testLegality )
7577             {   /* [HGM] Some more adjudications for obstinate engines */
7578                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7579                 static int moveCount = 6;
7580                 ChessMove result;
7581                 char *reason = NULL;
7582
7583                 /* Count what is on board. */
7584                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7585
7586                 /* Some material-based adjudications that have to be made before stalemate test */
7587                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7588                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7589                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7590                      if(canAdjudicate && appData.checkMates) {
7591                          if(engineOpponent)
7592                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7593                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7594                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7595                          return 1;
7596                      }
7597                 }
7598
7599                 /* Bare King in Shatranj (loses) or Losers (wins) */
7600                 if( nrW == 1 || nrB == 1) {
7601                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7602                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7603                      if(canAdjudicate && appData.checkMates) {
7604                          if(engineOpponent)
7605                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7606                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7607                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7608                          return 1;
7609                      }
7610                   } else
7611                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7612                   {    /* bare King */
7613                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7614                         if(canAdjudicate && appData.checkMates) {
7615                             /* but only adjudicate if adjudication enabled */
7616                             if(engineOpponent)
7617                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7618                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7619                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7620                             return 1;
7621                         }
7622                   }
7623                 } else bare = 1;
7624
7625
7626             // don't wait for engine to announce game end if we can judge ourselves
7627             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7628               case MT_CHECK:
7629                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7630                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7631                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7632                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7633                             checkCnt++;
7634                         if(checkCnt >= 2) {
7635                             reason = "Xboard adjudication: 3rd check";
7636                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7637                             break;
7638                         }
7639                     }
7640                 }
7641               case MT_NONE:
7642               default:
7643                 break;
7644               case MT_STALEMATE:
7645               case MT_STAINMATE:
7646                 reason = "Xboard adjudication: Stalemate";
7647                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7648                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7649                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7650                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7651                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7652                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7653                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7654                                                                         EP_CHECKMATE : EP_WINS);
7655                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7656                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7657                 }
7658                 break;
7659               case MT_CHECKMATE:
7660                 reason = "Xboard adjudication: Checkmate";
7661                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7662                 break;
7663             }
7664
7665                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7666                     case EP_STALEMATE:
7667                         result = GameIsDrawn; break;
7668                     case EP_CHECKMATE:
7669                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7670                     case EP_WINS:
7671                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7672                     default:
7673                         result = EndOfFile;
7674                 }
7675                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7676                     if(engineOpponent)
7677                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7678                     GameEnds( result, reason, GE_XBOARD );
7679                     return 1;
7680                 }
7681
7682                 /* Next absolutely insufficient mating material. */
7683                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7684                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7685                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7686
7687                      /* always flag draws, for judging claims */
7688                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7689
7690                      if(canAdjudicate && appData.materialDraws) {
7691                          /* but only adjudicate them if adjudication enabled */
7692                          if(engineOpponent) {
7693                            SendToProgram("force\n", engineOpponent); // suppress reply
7694                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7695                          }
7696                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7697                          return 1;
7698                      }
7699                 }
7700
7701                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7702                 if(gameInfo.variant == VariantXiangqi ?
7703                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7704                  : nrW + nrB == 4 &&
7705                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7706                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7707                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7708                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7709                    ) ) {
7710                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7711                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7712                           if(engineOpponent) {
7713                             SendToProgram("force\n", engineOpponent); // suppress reply
7714                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7715                           }
7716                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7717                           return 1;
7718                      }
7719                 } else moveCount = 6;
7720             }
7721
7722         // Repetition draws and 50-move rule can be applied independently of legality testing
7723
7724                 /* Check for rep-draws */
7725                 count = 0;
7726                 for(k = forwardMostMove-2;
7727                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7728                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7729                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7730                     k-=2)
7731                 {   int rights=0;
7732                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7733                         /* compare castling rights */
7734                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7735                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7736                                 rights++; /* King lost rights, while rook still had them */
7737                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7738                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7739                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7740                                    rights++; /* but at least one rook lost them */
7741                         }
7742                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7743                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7744                                 rights++;
7745                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7746                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7747                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7748                                    rights++;
7749                         }
7750                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7751                             && appData.drawRepeats > 1) {
7752                              /* adjudicate after user-specified nr of repeats */
7753                              int result = GameIsDrawn;
7754                              char *details = "XBoard adjudication: repetition draw";
7755                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7756                                 // [HGM] xiangqi: check for forbidden perpetuals
7757                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7758                                 for(m=forwardMostMove; m>k; m-=2) {
7759                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7760                                         ourPerpetual = 0; // the current mover did not always check
7761                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7762                                         hisPerpetual = 0; // the opponent did not always check
7763                                 }
7764                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7765                                                                         ourPerpetual, hisPerpetual);
7766                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7767                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7768                                     details = "Xboard adjudication: perpetual checking";
7769                                 } else
7770                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7771                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7772                                 } else
7773                                 // Now check for perpetual chases
7774                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7775                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7776                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7777                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7778                                         static char resdet[MSG_SIZ];
7779                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7780                                         details = resdet;
7781                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7782                                     } else
7783                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7784                                         break; // Abort repetition-checking loop.
7785                                 }
7786                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7787                              }
7788                              if(engineOpponent) {
7789                                SendToProgram("force\n", engineOpponent); // suppress reply
7790                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7791                              }
7792                              GameEnds( result, details, GE_XBOARD );
7793                              return 1;
7794                         }
7795                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7796                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7797                     }
7798                 }
7799
7800                 /* Now we test for 50-move draws. Determine ply count */
7801                 count = forwardMostMove;
7802                 /* look for last irreversble move */
7803                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7804                     count--;
7805                 /* if we hit starting position, add initial plies */
7806                 if( count == backwardMostMove )
7807                     count -= initialRulePlies;
7808                 count = forwardMostMove - count;
7809                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7810                         // adjust reversible move counter for checks in Xiangqi
7811                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7812                         if(i < backwardMostMove) i = backwardMostMove;
7813                         while(i <= forwardMostMove) {
7814                                 lastCheck = inCheck; // check evasion does not count
7815                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7816                                 if(inCheck || lastCheck) count--; // check does not count
7817                                 i++;
7818                         }
7819                 }
7820                 if( count >= 100)
7821                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7822                          /* this is used to judge if draw claims are legal */
7823                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7824                          if(engineOpponent) {
7825                            SendToProgram("force\n", engineOpponent); // suppress reply
7826                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7827                          }
7828                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7829                          return 1;
7830                 }
7831
7832                 /* if draw offer is pending, treat it as a draw claim
7833                  * when draw condition present, to allow engines a way to
7834                  * claim draws before making their move to avoid a race
7835                  * condition occurring after their move
7836                  */
7837                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7838                          char *p = NULL;
7839                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7840                              p = "Draw claim: 50-move rule";
7841                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7842                              p = "Draw claim: 3-fold repetition";
7843                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7844                              p = "Draw claim: insufficient mating material";
7845                          if( p != NULL && canAdjudicate) {
7846                              if(engineOpponent) {
7847                                SendToProgram("force\n", engineOpponent); // suppress reply
7848                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7849                              }
7850                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7851                              return 1;
7852                          }
7853                 }
7854
7855                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7856                     if(engineOpponent) {
7857                       SendToProgram("force\n", engineOpponent); // suppress reply
7858                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7859                     }
7860                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7861                     return 1;
7862                 }
7863         return 0;
7864 }
7865
7866 char *
7867 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7868 {   // [HGM] book: this routine intercepts moves to simulate book replies
7869     char *bookHit = NULL;
7870
7871     //first determine if the incoming move brings opponent into his book
7872     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7873         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7874     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7875     if(bookHit != NULL && !cps->bookSuspend) {
7876         // make sure opponent is not going to reply after receiving move to book position
7877         SendToProgram("force\n", cps);
7878         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7879     }
7880     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7881     // now arrange restart after book miss
7882     if(bookHit) {
7883         // after a book hit we never send 'go', and the code after the call to this routine
7884         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7885         char buf[MSG_SIZ], *move = bookHit;
7886         if(cps->useSAN) {
7887             int fromX, fromY, toX, toY;
7888             char promoChar;
7889             ChessMove moveType;
7890             move = buf + 30;
7891             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7892                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7893                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7894                                     PosFlags(forwardMostMove),
7895                                     fromY, fromX, toY, toX, promoChar, move);
7896             } else {
7897                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7898                 bookHit = NULL;
7899             }
7900         }
7901         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7902         SendToProgram(buf, cps);
7903         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7904     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7905         SendToProgram("go\n", cps);
7906         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7907     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7908         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7909             SendToProgram("go\n", cps);
7910         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7911     }
7912     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7913 }
7914
7915 char *savedMessage;
7916 ChessProgramState *savedState;
7917 void
7918 DeferredBookMove (void)
7919 {
7920         if(savedState->lastPing != savedState->lastPong)
7921                     ScheduleDelayedEvent(DeferredBookMove, 10);
7922         else
7923         HandleMachineMove(savedMessage, savedState);
7924 }
7925
7926 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7927
7928 void
7929 HandleMachineMove (char *message, ChessProgramState *cps)
7930 {
7931     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7932     char realname[MSG_SIZ];
7933     int fromX, fromY, toX, toY;
7934     ChessMove moveType;
7935     char promoChar;
7936     char *p, *pv=buf1;
7937     int machineWhite;
7938     char *bookHit;
7939
7940     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7941         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7942         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7943             DisplayError(_("Invalid pairing from pairing engine"), 0);
7944             return;
7945         }
7946         pairingReceived = 1;
7947         NextMatchGame();
7948         return; // Skim the pairing messages here.
7949     }
7950
7951     cps->userError = 0;
7952
7953 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7954     /*
7955      * Kludge to ignore BEL characters
7956      */
7957     while (*message == '\007') message++;
7958
7959     /*
7960      * [HGM] engine debug message: ignore lines starting with '#' character
7961      */
7962     if(cps->debug && *message == '#') return;
7963
7964     /*
7965      * Look for book output
7966      */
7967     if (cps == &first && bookRequested) {
7968         if (message[0] == '\t' || message[0] == ' ') {
7969             /* Part of the book output is here; append it */
7970             strcat(bookOutput, message);
7971             strcat(bookOutput, "  \n");
7972             return;
7973         } else if (bookOutput[0] != NULLCHAR) {
7974             /* All of book output has arrived; display it */
7975             char *p = bookOutput;
7976             while (*p != NULLCHAR) {
7977                 if (*p == '\t') *p = ' ';
7978                 p++;
7979             }
7980             DisplayInformation(bookOutput);
7981             bookRequested = FALSE;
7982             /* Fall through to parse the current output */
7983         }
7984     }
7985
7986     /*
7987      * Look for machine move.
7988      */
7989     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7990         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7991     {
7992         /* This method is only useful on engines that support ping */
7993         if (cps->lastPing != cps->lastPong) {
7994           if (gameMode == BeginningOfGame) {
7995             /* Extra move from before last new; ignore */
7996             if (appData.debugMode) {
7997                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7998             }
7999           } else {
8000             if (appData.debugMode) {
8001                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8002                         cps->which, gameMode);
8003             }
8004
8005             SendToProgram("undo\n", cps);
8006           }
8007           return;
8008         }
8009
8010         switch (gameMode) {
8011           case BeginningOfGame:
8012             /* Extra move from before last reset; ignore */
8013             if (appData.debugMode) {
8014                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8015             }
8016             return;
8017
8018           case EndOfGame:
8019           case IcsIdle:
8020           default:
8021             /* Extra move after we tried to stop.  The mode test is
8022                not a reliable way of detecting this problem, but it's
8023                the best we can do on engines that don't support ping.
8024             */
8025             if (appData.debugMode) {
8026                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8027                         cps->which, gameMode);
8028             }
8029             SendToProgram("undo\n", cps);
8030             return;
8031
8032           case MachinePlaysWhite:
8033           case IcsPlayingWhite:
8034             machineWhite = TRUE;
8035             break;
8036
8037           case MachinePlaysBlack:
8038           case IcsPlayingBlack:
8039             machineWhite = FALSE;
8040             break;
8041
8042           case TwoMachinesPlay:
8043             machineWhite = (cps->twoMachinesColor[0] == 'w');
8044             break;
8045         }
8046         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8047             if (appData.debugMode) {
8048                 fprintf(debugFP,
8049                         "Ignoring move out of turn by %s, gameMode %d"
8050                         ", forwardMost %d\n",
8051                         cps->which, gameMode, forwardMostMove);
8052             }
8053             return;
8054         }
8055
8056         if(cps->alphaRank) AlphaRank(machineMove, 4);
8057         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8058                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8059             /* Machine move could not be parsed; ignore it. */
8060           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8061                     machineMove, _(cps->which));
8062             DisplayError(buf1, 0);
8063             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8064                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8065             if (gameMode == TwoMachinesPlay) {
8066               GameEnds(machineWhite ? BlackWins : WhiteWins,
8067                        buf1, GE_XBOARD);
8068             }
8069             return;
8070         }
8071
8072         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8073         /* So we have to redo legality test with true e.p. status here,  */
8074         /* to make sure an illegal e.p. capture does not slip through,   */
8075         /* to cause a forfeit on a justified illegal-move complaint      */
8076         /* of the opponent.                                              */
8077         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8078            ChessMove moveType;
8079            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8080                              fromY, fromX, toY, toX, promoChar);
8081             if(moveType == IllegalMove) {
8082               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8083                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8084                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8085                            buf1, GE_XBOARD);
8086                 return;
8087            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8088            /* [HGM] Kludge to handle engines that send FRC-style castling
8089               when they shouldn't (like TSCP-Gothic) */
8090            switch(moveType) {
8091              case WhiteASideCastleFR:
8092              case BlackASideCastleFR:
8093                toX+=2;
8094                currentMoveString[2]++;
8095                break;
8096              case WhiteHSideCastleFR:
8097              case BlackHSideCastleFR:
8098                toX--;
8099                currentMoveString[2]--;
8100                break;
8101              default: ; // nothing to do, but suppresses warning of pedantic compilers
8102            }
8103         }
8104         hintRequested = FALSE;
8105         lastHint[0] = NULLCHAR;
8106         bookRequested = FALSE;
8107         /* Program may be pondering now */
8108         cps->maybeThinking = TRUE;
8109         if (cps->sendTime == 2) cps->sendTime = 1;
8110         if (cps->offeredDraw) cps->offeredDraw--;
8111
8112         /* [AS] Save move info*/
8113         pvInfoList[ forwardMostMove ].score = programStats.score;
8114         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8115         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8116
8117         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8118
8119         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8120         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8121             int count = 0;
8122
8123             while( count < adjudicateLossPlies ) {
8124                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8125
8126                 if( count & 1 ) {
8127                     score = -score; /* Flip score for winning side */
8128                 }
8129
8130                 if( score > adjudicateLossThreshold ) {
8131                     break;
8132                 }
8133
8134                 count++;
8135             }
8136
8137             if( count >= adjudicateLossPlies ) {
8138                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8139
8140                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8141                     "Xboard adjudication",
8142                     GE_XBOARD );
8143
8144                 return;
8145             }
8146         }
8147
8148         if(Adjudicate(cps)) {
8149             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8150             return; // [HGM] adjudicate: for all automatic game ends
8151         }
8152
8153 #if ZIPPY
8154         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8155             first.initDone) {
8156           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8157                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8158                 SendToICS("draw ");
8159                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8160           }
8161           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8162           ics_user_moved = 1;
8163           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8164                 char buf[3*MSG_SIZ];
8165
8166                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8167                         programStats.score / 100.,
8168                         programStats.depth,
8169                         programStats.time / 100.,
8170                         (unsigned int)programStats.nodes,
8171                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8172                         programStats.movelist);
8173                 SendToICS(buf);
8174 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8175           }
8176         }
8177 #endif
8178
8179         /* [AS] Clear stats for next move */
8180         ClearProgramStats();
8181         thinkOutput[0] = NULLCHAR;
8182         hiddenThinkOutputState = 0;
8183
8184         bookHit = NULL;
8185         if (gameMode == TwoMachinesPlay) {
8186             /* [HGM] relaying draw offers moved to after reception of move */
8187             /* and interpreting offer as claim if it brings draw condition */
8188             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8189                 SendToProgram("draw\n", cps->other);
8190             }
8191             if (cps->other->sendTime) {
8192                 SendTimeRemaining(cps->other,
8193                                   cps->other->twoMachinesColor[0] == 'w');
8194             }
8195             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8196             if (firstMove && !bookHit) {
8197                 firstMove = FALSE;
8198                 if (cps->other->useColors) {
8199                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8200                 }
8201                 SendToProgram("go\n", cps->other);
8202             }
8203             cps->other->maybeThinking = TRUE;
8204         }
8205
8206         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8207
8208         if (!pausing && appData.ringBellAfterMoves) {
8209             RingBell();
8210         }
8211
8212         /*
8213          * Reenable menu items that were disabled while
8214          * machine was thinking
8215          */
8216         if (gameMode != TwoMachinesPlay)
8217             SetUserThinkingEnables();
8218
8219         // [HGM] book: after book hit opponent has received move and is now in force mode
8220         // force the book reply into it, and then fake that it outputted this move by jumping
8221         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8222         if(bookHit) {
8223                 static char bookMove[MSG_SIZ]; // a bit generous?
8224
8225                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8226                 strcat(bookMove, bookHit);
8227                 message = bookMove;
8228                 cps = cps->other;
8229                 programStats.nodes = programStats.depth = programStats.time =
8230                 programStats.score = programStats.got_only_move = 0;
8231                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8232
8233                 if(cps->lastPing != cps->lastPong) {
8234                     savedMessage = message; // args for deferred call
8235                     savedState = cps;
8236                     ScheduleDelayedEvent(DeferredBookMove, 10);
8237                     return;
8238                 }
8239                 goto FakeBookMove;
8240         }
8241
8242         return;
8243     }
8244
8245     /* Set special modes for chess engines.  Later something general
8246      *  could be added here; for now there is just one kludge feature,
8247      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8248      *  when "xboard" is given as an interactive command.
8249      */
8250     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8251         cps->useSigint = FALSE;
8252         cps->useSigterm = FALSE;
8253     }
8254     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8255       ParseFeatures(message+8, cps);
8256       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8257     }
8258
8259     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8260                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8261       int dummy, s=6; char buf[MSG_SIZ];
8262       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8263       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8264       if(startedFromSetupPosition) return;
8265       ParseFEN(boards[0], &dummy, message+s);
8266       DrawPosition(TRUE, boards[0]);
8267       startedFromSetupPosition = TRUE;
8268       return;
8269     }
8270     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8271      * want this, I was asked to put it in, and obliged.
8272      */
8273     if (!strncmp(message, "setboard ", 9)) {
8274         Board initial_position;
8275
8276         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8277
8278         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8279             DisplayError(_("Bad FEN received from engine"), 0);
8280             return ;
8281         } else {
8282            Reset(TRUE, FALSE);
8283            CopyBoard(boards[0], initial_position);
8284            initialRulePlies = FENrulePlies;
8285            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8286            else gameMode = MachinePlaysBlack;
8287            DrawPosition(FALSE, boards[currentMove]);
8288         }
8289         return;
8290     }
8291
8292     /*
8293      * Look for communication commands
8294      */
8295     if (!strncmp(message, "telluser ", 9)) {
8296         if(message[9] == '\\' && message[10] == '\\')
8297             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8298         PlayTellSound();
8299         DisplayNote(message + 9);
8300         return;
8301     }
8302     if (!strncmp(message, "tellusererror ", 14)) {
8303         cps->userError = 1;
8304         if(message[14] == '\\' && message[15] == '\\')
8305             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8306         PlayTellSound();
8307         DisplayError(message + 14, 0);
8308         return;
8309     }
8310     if (!strncmp(message, "tellopponent ", 13)) {
8311       if (appData.icsActive) {
8312         if (loggedOn) {
8313           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8314           SendToICS(buf1);
8315         }
8316       } else {
8317         DisplayNote(message + 13);
8318       }
8319       return;
8320     }
8321     if (!strncmp(message, "tellothers ", 11)) {
8322       if (appData.icsActive) {
8323         if (loggedOn) {
8324           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8325           SendToICS(buf1);
8326         }
8327       }
8328       return;
8329     }
8330     if (!strncmp(message, "tellall ", 8)) {
8331       if (appData.icsActive) {
8332         if (loggedOn) {
8333           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8334           SendToICS(buf1);
8335         }
8336       } else {
8337         DisplayNote(message + 8);
8338       }
8339       return;
8340     }
8341     if (strncmp(message, "warning", 7) == 0) {
8342         /* Undocumented feature, use tellusererror in new code */
8343         DisplayError(message, 0);
8344         return;
8345     }
8346     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8347         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8348         strcat(realname, " query");
8349         AskQuestion(realname, buf2, buf1, cps->pr);
8350         return;
8351     }
8352     /* Commands from the engine directly to ICS.  We don't allow these to be
8353      *  sent until we are logged on. Crafty kibitzes have been known to
8354      *  interfere with the login process.
8355      */
8356     if (loggedOn) {
8357         if (!strncmp(message, "tellics ", 8)) {
8358             SendToICS(message + 8);
8359             SendToICS("\n");
8360             return;
8361         }
8362         if (!strncmp(message, "tellicsnoalias ", 15)) {
8363             SendToICS(ics_prefix);
8364             SendToICS(message + 15);
8365             SendToICS("\n");
8366             return;
8367         }
8368         /* The following are for backward compatibility only */
8369         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8370             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8371             SendToICS(ics_prefix);
8372             SendToICS(message);
8373             SendToICS("\n");
8374             return;
8375         }
8376     }
8377     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8378         return;
8379     }
8380     /*
8381      * If the move is illegal, cancel it and redraw the board.
8382      * Also deal with other error cases.  Matching is rather loose
8383      * here to accommodate engines written before the spec.
8384      */
8385     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8386         strncmp(message, "Error", 5) == 0) {
8387         if (StrStr(message, "name") ||
8388             StrStr(message, "rating") || StrStr(message, "?") ||
8389             StrStr(message, "result") || StrStr(message, "board") ||
8390             StrStr(message, "bk") || StrStr(message, "computer") ||
8391             StrStr(message, "variant") || StrStr(message, "hint") ||
8392             StrStr(message, "random") || StrStr(message, "depth") ||
8393             StrStr(message, "accepted")) {
8394             return;
8395         }
8396         if (StrStr(message, "protover")) {
8397           /* Program is responding to input, so it's apparently done
8398              initializing, and this error message indicates it is
8399              protocol version 1.  So we don't need to wait any longer
8400              for it to initialize and send feature commands. */
8401           FeatureDone(cps, 1);
8402           cps->protocolVersion = 1;
8403           return;
8404         }
8405         cps->maybeThinking = FALSE;
8406
8407         if (StrStr(message, "draw")) {
8408             /* Program doesn't have "draw" command */
8409             cps->sendDrawOffers = 0;
8410             return;
8411         }
8412         if (cps->sendTime != 1 &&
8413             (StrStr(message, "time") || StrStr(message, "otim"))) {
8414           /* Program apparently doesn't have "time" or "otim" command */
8415           cps->sendTime = 0;
8416           return;
8417         }
8418         if (StrStr(message, "analyze")) {
8419             cps->analysisSupport = FALSE;
8420             cps->analyzing = FALSE;
8421 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8422             EditGameEvent(); // [HGM] try to preserve loaded game
8423             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8424             DisplayError(buf2, 0);
8425             return;
8426         }
8427         if (StrStr(message, "(no matching move)st")) {
8428           /* Special kludge for GNU Chess 4 only */
8429           cps->stKludge = TRUE;
8430           SendTimeControl(cps, movesPerSession, timeControl,
8431                           timeIncrement, appData.searchDepth,
8432                           searchTime);
8433           return;
8434         }
8435         if (StrStr(message, "(no matching move)sd")) {
8436           /* Special kludge for GNU Chess 4 only */
8437           cps->sdKludge = TRUE;
8438           SendTimeControl(cps, movesPerSession, timeControl,
8439                           timeIncrement, appData.searchDepth,
8440                           searchTime);
8441           return;
8442         }
8443         if (!StrStr(message, "llegal")) {
8444             return;
8445         }
8446         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8447             gameMode == IcsIdle) return;
8448         if (forwardMostMove <= backwardMostMove) return;
8449         if (pausing) PauseEvent();
8450       if(appData.forceIllegal) {
8451             // [HGM] illegal: machine refused move; force position after move into it
8452           SendToProgram("force\n", cps);
8453           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8454                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8455                 // when black is to move, while there might be nothing on a2 or black
8456                 // might already have the move. So send the board as if white has the move.
8457                 // But first we must change the stm of the engine, as it refused the last move
8458                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8459                 if(WhiteOnMove(forwardMostMove)) {
8460                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8461                     SendBoard(cps, forwardMostMove); // kludgeless board
8462                 } else {
8463                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8464                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8465                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8466                 }
8467           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8468             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8469                  gameMode == TwoMachinesPlay)
8470               SendToProgram("go\n", cps);
8471             return;
8472       } else
8473         if (gameMode == PlayFromGameFile) {
8474             /* Stop reading this game file */
8475             gameMode = EditGame;
8476             ModeHighlight();
8477         }
8478         /* [HGM] illegal-move claim should forfeit game when Xboard */
8479         /* only passes fully legal moves                            */
8480         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8481             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8482                                 "False illegal-move claim", GE_XBOARD );
8483             return; // do not take back move we tested as valid
8484         }
8485         currentMove = forwardMostMove-1;
8486         DisplayMove(currentMove-1); /* before DisplayMoveError */
8487         SwitchClocks(forwardMostMove-1); // [HGM] race
8488         DisplayBothClocks();
8489         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8490                 parseList[currentMove], _(cps->which));
8491         DisplayMoveError(buf1);
8492         DrawPosition(FALSE, boards[currentMove]);
8493
8494         SetUserThinkingEnables();
8495         return;
8496     }
8497     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8498         /* Program has a broken "time" command that
8499            outputs a string not ending in newline.
8500            Don't use it. */
8501         cps->sendTime = 0;
8502     }
8503
8504     /*
8505      * If chess program startup fails, exit with an error message.
8506      * Attempts to recover here are futile. [HGM] Well, we try anyway
8507      */
8508     if ((StrStr(message, "unknown host") != NULL)
8509         || (StrStr(message, "No remote directory") != NULL)
8510         || (StrStr(message, "not found") != NULL)
8511         || (StrStr(message, "No such file") != NULL)
8512         || (StrStr(message, "can't alloc") != NULL)
8513         || (StrStr(message, "Permission denied") != NULL)) {
8514
8515         cps->maybeThinking = FALSE;
8516         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8517                 _(cps->which), cps->program, cps->host, message);
8518         RemoveInputSource(cps->isr);
8519         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8520             cps->isr = NULL;
8521             DestroyChildProcess(cps->pr, 9 ); // just to be sure
8522             cps->pr = NoProc; 
8523             if(cps == &first) {
8524                 appData.noChessProgram = TRUE;
8525                 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8526                 gameMode = BeginningOfGame; ModeHighlight();
8527                 SetNCPMode();
8528             }
8529             if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8530             DisplayMessage("", ""); // erase waiting message
8531             DisplayError(buf1, 0);
8532         }
8533         return;
8534     }
8535
8536     /*
8537      * Look for hint output
8538      */
8539     if (sscanf(message, "Hint: %s", buf1) == 1) {
8540         if (cps == &first && hintRequested) {
8541             hintRequested = FALSE;
8542             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8543                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8544                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8545                                     PosFlags(forwardMostMove),
8546                                     fromY, fromX, toY, toX, promoChar, buf1);
8547                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8548                 DisplayInformation(buf2);
8549             } else {
8550                 /* Hint move could not be parsed!? */
8551               snprintf(buf2, sizeof(buf2),
8552                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8553                         buf1, _(cps->which));
8554                 DisplayError(buf2, 0);
8555             }
8556         } else {
8557           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8558         }
8559         return;
8560     }
8561
8562     /*
8563      * Ignore other messages if game is not in progress
8564      */
8565     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8566         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8567
8568     /*
8569      * look for win, lose, draw, or draw offer
8570      */
8571     if (strncmp(message, "1-0", 3) == 0) {
8572         char *p, *q, *r = "";
8573         p = strchr(message, '{');
8574         if (p) {
8575             q = strchr(p, '}');
8576             if (q) {
8577                 *q = NULLCHAR;
8578                 r = p + 1;
8579             }
8580         }
8581         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8582         return;
8583     } else if (strncmp(message, "0-1", 3) == 0) {
8584         char *p, *q, *r = "";
8585         p = strchr(message, '{');
8586         if (p) {
8587             q = strchr(p, '}');
8588             if (q) {
8589                 *q = NULLCHAR;
8590                 r = p + 1;
8591             }
8592         }
8593         /* Kludge for Arasan 4.1 bug */
8594         if (strcmp(r, "Black resigns") == 0) {
8595             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8596             return;
8597         }
8598         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8599         return;
8600     } else if (strncmp(message, "1/2", 3) == 0) {
8601         char *p, *q, *r = "";
8602         p = strchr(message, '{');
8603         if (p) {
8604             q = strchr(p, '}');
8605             if (q) {
8606                 *q = NULLCHAR;
8607                 r = p + 1;
8608             }
8609         }
8610
8611         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8612         return;
8613
8614     } else if (strncmp(message, "White resign", 12) == 0) {
8615         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8616         return;
8617     } else if (strncmp(message, "Black resign", 12) == 0) {
8618         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8619         return;
8620     } else if (strncmp(message, "White matches", 13) == 0 ||
8621                strncmp(message, "Black matches", 13) == 0   ) {
8622         /* [HGM] ignore GNUShogi noises */
8623         return;
8624     } else if (strncmp(message, "White", 5) == 0 &&
8625                message[5] != '(' &&
8626                StrStr(message, "Black") == NULL) {
8627         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8628         return;
8629     } else if (strncmp(message, "Black", 5) == 0 &&
8630                message[5] != '(') {
8631         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8632         return;
8633     } else if (strcmp(message, "resign") == 0 ||
8634                strcmp(message, "computer resigns") == 0) {
8635         switch (gameMode) {
8636           case MachinePlaysBlack:
8637           case IcsPlayingBlack:
8638             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8639             break;
8640           case MachinePlaysWhite:
8641           case IcsPlayingWhite:
8642             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8643             break;
8644           case TwoMachinesPlay:
8645             if (cps->twoMachinesColor[0] == 'w')
8646               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8647             else
8648               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8649             break;
8650           default:
8651             /* can't happen */
8652             break;
8653         }
8654         return;
8655     } else if (strncmp(message, "opponent mates", 14) == 0) {
8656         switch (gameMode) {
8657           case MachinePlaysBlack:
8658           case IcsPlayingBlack:
8659             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8660             break;
8661           case MachinePlaysWhite:
8662           case IcsPlayingWhite:
8663             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8664             break;
8665           case TwoMachinesPlay:
8666             if (cps->twoMachinesColor[0] == 'w')
8667               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8668             else
8669               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8670             break;
8671           default:
8672             /* can't happen */
8673             break;
8674         }
8675         return;
8676     } else if (strncmp(message, "computer mates", 14) == 0) {
8677         switch (gameMode) {
8678           case MachinePlaysBlack:
8679           case IcsPlayingBlack:
8680             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8681             break;
8682           case MachinePlaysWhite:
8683           case IcsPlayingWhite:
8684             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8685             break;
8686           case TwoMachinesPlay:
8687             if (cps->twoMachinesColor[0] == 'w')
8688               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8689             else
8690               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8691             break;
8692           default:
8693             /* can't happen */
8694             break;
8695         }
8696         return;
8697     } else if (strncmp(message, "checkmate", 9) == 0) {
8698         if (WhiteOnMove(forwardMostMove)) {
8699             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8700         } else {
8701             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8702         }
8703         return;
8704     } else if (strstr(message, "Draw") != NULL ||
8705                strstr(message, "game is a draw") != NULL) {
8706         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8707         return;
8708     } else if (strstr(message, "offer") != NULL &&
8709                strstr(message, "draw") != NULL) {
8710 #if ZIPPY
8711         if (appData.zippyPlay && first.initDone) {
8712             /* Relay offer to ICS */
8713             SendToICS(ics_prefix);
8714             SendToICS("draw\n");
8715         }
8716 #endif
8717         cps->offeredDraw = 2; /* valid until this engine moves twice */
8718         if (gameMode == TwoMachinesPlay) {
8719             if (cps->other->offeredDraw) {
8720                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8721             /* [HGM] in two-machine mode we delay relaying draw offer      */
8722             /* until after we also have move, to see if it is really claim */
8723             }
8724         } else if (gameMode == MachinePlaysWhite ||
8725                    gameMode == MachinePlaysBlack) {
8726           if (userOfferedDraw) {
8727             DisplayInformation(_("Machine accepts your draw offer"));
8728             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8729           } else {
8730             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8731           }
8732         }
8733     }
8734
8735
8736     /*
8737      * Look for thinking output
8738      */
8739     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8740           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8741                                 ) {
8742         int plylev, mvleft, mvtot, curscore, time;
8743         char mvname[MOVE_LEN];
8744         u64 nodes; // [DM]
8745         char plyext;
8746         int ignore = FALSE;
8747         int prefixHint = FALSE;
8748         mvname[0] = NULLCHAR;
8749
8750         switch (gameMode) {
8751           case MachinePlaysBlack:
8752           case IcsPlayingBlack:
8753             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8754             break;
8755           case MachinePlaysWhite:
8756           case IcsPlayingWhite:
8757             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8758             break;
8759           case AnalyzeMode:
8760           case AnalyzeFile:
8761             break;
8762           case IcsObserving: /* [DM] icsEngineAnalyze */
8763             if (!appData.icsEngineAnalyze) ignore = TRUE;
8764             break;
8765           case TwoMachinesPlay:
8766             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8767                 ignore = TRUE;
8768             }
8769             break;
8770           default:
8771             ignore = TRUE;
8772             break;
8773         }
8774
8775         if (!ignore) {
8776             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8777             buf1[0] = NULLCHAR;
8778             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8779                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8780
8781                 if (plyext != ' ' && plyext != '\t') {
8782                     time *= 100;
8783                 }
8784
8785                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8786                 if( cps->scoreIsAbsolute &&
8787                     ( gameMode == MachinePlaysBlack ||
8788                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8789                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8790                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8791                      !WhiteOnMove(currentMove)
8792                     ) )
8793                 {
8794                     curscore = -curscore;
8795                 }
8796
8797                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8798
8799                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8800                         char buf[MSG_SIZ];
8801                         FILE *f;
8802                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8803                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8804                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8805                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8806                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8807                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8808                                 fclose(f);
8809                         } else DisplayError(_("failed writing PV"), 0);
8810                 }
8811
8812                 tempStats.depth = plylev;
8813                 tempStats.nodes = nodes;
8814                 tempStats.time = time;
8815                 tempStats.score = curscore;
8816                 tempStats.got_only_move = 0;
8817
8818                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8819                         int ticklen;
8820
8821                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8822                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8823                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8824                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8825                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8826                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8827                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8828                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8829                 }
8830
8831                 /* Buffer overflow protection */
8832                 if (pv[0] != NULLCHAR) {
8833                     if (strlen(pv) >= sizeof(tempStats.movelist)
8834                         && appData.debugMode) {
8835                         fprintf(debugFP,
8836                                 "PV is too long; using the first %u bytes.\n",
8837                                 (unsigned) sizeof(tempStats.movelist) - 1);
8838                     }
8839
8840                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8841                 } else {
8842                     sprintf(tempStats.movelist, " no PV\n");
8843                 }
8844
8845                 if (tempStats.seen_stat) {
8846                     tempStats.ok_to_send = 1;
8847                 }
8848
8849                 if (strchr(tempStats.movelist, '(') != NULL) {
8850                     tempStats.line_is_book = 1;
8851                     tempStats.nr_moves = 0;
8852                     tempStats.moves_left = 0;
8853                 } else {
8854                     tempStats.line_is_book = 0;
8855                 }
8856
8857                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8858                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8859
8860                 SendProgramStatsToFrontend( cps, &tempStats );
8861
8862                 /*
8863                     [AS] Protect the thinkOutput buffer from overflow... this
8864                     is only useful if buf1 hasn't overflowed first!
8865                 */
8866                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8867                          plylev,
8868                          (gameMode == TwoMachinesPlay ?
8869                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8870                          ((double) curscore) / 100.0,
8871                          prefixHint ? lastHint : "",
8872                          prefixHint ? " " : "" );
8873
8874                 if( buf1[0] != NULLCHAR ) {
8875                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8876
8877                     if( strlen(pv) > max_len ) {
8878                         if( appData.debugMode) {
8879                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8880                         }
8881                         pv[max_len+1] = '\0';
8882                     }
8883
8884                     strcat( thinkOutput, pv);
8885                 }
8886
8887                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8888                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8889                     DisplayMove(currentMove - 1);
8890                 }
8891                 return;
8892
8893             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8894                 /* crafty (9.25+) says "(only move) <move>"
8895                  * if there is only 1 legal move
8896                  */
8897                 sscanf(p, "(only move) %s", buf1);
8898                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8899                 sprintf(programStats.movelist, "%s (only move)", buf1);
8900                 programStats.depth = 1;
8901                 programStats.nr_moves = 1;
8902                 programStats.moves_left = 1;
8903                 programStats.nodes = 1;
8904                 programStats.time = 1;
8905                 programStats.got_only_move = 1;
8906
8907                 /* Not really, but we also use this member to
8908                    mean "line isn't going to change" (Crafty
8909                    isn't searching, so stats won't change) */
8910                 programStats.line_is_book = 1;
8911
8912                 SendProgramStatsToFrontend( cps, &programStats );
8913
8914                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8915                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8916                     DisplayMove(currentMove - 1);
8917                 }
8918                 return;
8919             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8920                               &time, &nodes, &plylev, &mvleft,
8921                               &mvtot, mvname) >= 5) {
8922                 /* The stat01: line is from Crafty (9.29+) in response
8923                    to the "." command */
8924                 programStats.seen_stat = 1;
8925                 cps->maybeThinking = TRUE;
8926
8927                 if (programStats.got_only_move || !appData.periodicUpdates)
8928                   return;
8929
8930                 programStats.depth = plylev;
8931                 programStats.time = time;
8932                 programStats.nodes = nodes;
8933                 programStats.moves_left = mvleft;
8934                 programStats.nr_moves = mvtot;
8935                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8936                 programStats.ok_to_send = 1;
8937                 programStats.movelist[0] = '\0';
8938
8939                 SendProgramStatsToFrontend( cps, &programStats );
8940
8941                 return;
8942
8943             } else if (strncmp(message,"++",2) == 0) {
8944                 /* Crafty 9.29+ outputs this */
8945                 programStats.got_fail = 2;
8946                 return;
8947
8948             } else if (strncmp(message,"--",2) == 0) {
8949                 /* Crafty 9.29+ outputs this */
8950                 programStats.got_fail = 1;
8951                 return;
8952
8953             } else if (thinkOutput[0] != NULLCHAR &&
8954                        strncmp(message, "    ", 4) == 0) {
8955                 unsigned message_len;
8956
8957                 p = message;
8958                 while (*p && *p == ' ') p++;
8959
8960                 message_len = strlen( p );
8961
8962                 /* [AS] Avoid buffer overflow */
8963                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8964                     strcat(thinkOutput, " ");
8965                     strcat(thinkOutput, p);
8966                 }
8967
8968                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8969                     strcat(programStats.movelist, " ");
8970                     strcat(programStats.movelist, p);
8971                 }
8972
8973                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8974                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8975                     DisplayMove(currentMove - 1);
8976                 }
8977                 return;
8978             }
8979         }
8980         else {
8981             buf1[0] = NULLCHAR;
8982
8983             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8984                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8985             {
8986                 ChessProgramStats cpstats;
8987
8988                 if (plyext != ' ' && plyext != '\t') {
8989                     time *= 100;
8990                 }
8991
8992                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8993                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8994                     curscore = -curscore;
8995                 }
8996
8997                 cpstats.depth = plylev;
8998                 cpstats.nodes = nodes;
8999                 cpstats.time = time;
9000                 cpstats.score = curscore;
9001                 cpstats.got_only_move = 0;
9002                 cpstats.movelist[0] = '\0';
9003
9004                 if (buf1[0] != NULLCHAR) {
9005                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9006                 }
9007
9008                 cpstats.ok_to_send = 0;
9009                 cpstats.line_is_book = 0;
9010                 cpstats.nr_moves = 0;
9011                 cpstats.moves_left = 0;
9012
9013                 SendProgramStatsToFrontend( cps, &cpstats );
9014             }
9015         }
9016     }
9017 }
9018
9019
9020 /* Parse a game score from the character string "game", and
9021    record it as the history of the current game.  The game
9022    score is NOT assumed to start from the standard position.
9023    The display is not updated in any way.
9024    */
9025 void
9026 ParseGameHistory (char *game)
9027 {
9028     ChessMove moveType;
9029     int fromX, fromY, toX, toY, boardIndex;
9030     char promoChar;
9031     char *p, *q;
9032     char buf[MSG_SIZ];
9033
9034     if (appData.debugMode)
9035       fprintf(debugFP, "Parsing game history: %s\n", game);
9036
9037     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9038     gameInfo.site = StrSave(appData.icsHost);
9039     gameInfo.date = PGNDate();
9040     gameInfo.round = StrSave("-");
9041
9042     /* Parse out names of players */
9043     while (*game == ' ') game++;
9044     p = buf;
9045     while (*game != ' ') *p++ = *game++;
9046     *p = NULLCHAR;
9047     gameInfo.white = StrSave(buf);
9048     while (*game == ' ') game++;
9049     p = buf;
9050     while (*game != ' ' && *game != '\n') *p++ = *game++;
9051     *p = NULLCHAR;
9052     gameInfo.black = StrSave(buf);
9053
9054     /* Parse moves */
9055     boardIndex = blackPlaysFirst ? 1 : 0;
9056     yynewstr(game);
9057     for (;;) {
9058         yyboardindex = boardIndex;
9059         moveType = (ChessMove) Myylex();
9060         switch (moveType) {
9061           case IllegalMove:             /* maybe suicide chess, etc. */
9062   if (appData.debugMode) {
9063     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9064     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9065     setbuf(debugFP, NULL);
9066   }
9067           case WhitePromotion:
9068           case BlackPromotion:
9069           case WhiteNonPromotion:
9070           case BlackNonPromotion:
9071           case NormalMove:
9072           case WhiteCapturesEnPassant:
9073           case BlackCapturesEnPassant:
9074           case WhiteKingSideCastle:
9075           case WhiteQueenSideCastle:
9076           case BlackKingSideCastle:
9077           case BlackQueenSideCastle:
9078           case WhiteKingSideCastleWild:
9079           case WhiteQueenSideCastleWild:
9080           case BlackKingSideCastleWild:
9081           case BlackQueenSideCastleWild:
9082           /* PUSH Fabien */
9083           case WhiteHSideCastleFR:
9084           case WhiteASideCastleFR:
9085           case BlackHSideCastleFR:
9086           case BlackASideCastleFR:
9087           /* POP Fabien */
9088             fromX = currentMoveString[0] - AAA;
9089             fromY = currentMoveString[1] - ONE;
9090             toX = currentMoveString[2] - AAA;
9091             toY = currentMoveString[3] - ONE;
9092             promoChar = currentMoveString[4];
9093             break;
9094           case WhiteDrop:
9095           case BlackDrop:
9096             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9097             fromX = moveType == WhiteDrop ?
9098               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9099             (int) CharToPiece(ToLower(currentMoveString[0]));
9100             fromY = DROP_RANK;
9101             toX = currentMoveString[2] - AAA;
9102             toY = currentMoveString[3] - ONE;
9103             promoChar = NULLCHAR;
9104             break;
9105           case AmbiguousMove:
9106             /* bug? */
9107             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9108   if (appData.debugMode) {
9109     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9110     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9111     setbuf(debugFP, NULL);
9112   }
9113             DisplayError(buf, 0);
9114             return;
9115           case ImpossibleMove:
9116             /* bug? */
9117             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9118   if (appData.debugMode) {
9119     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9120     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9121     setbuf(debugFP, NULL);
9122   }
9123             DisplayError(buf, 0);
9124             return;
9125           case EndOfFile:
9126             if (boardIndex < backwardMostMove) {
9127                 /* Oops, gap.  How did that happen? */
9128                 DisplayError(_("Gap in move list"), 0);
9129                 return;
9130             }
9131             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9132             if (boardIndex > forwardMostMove) {
9133                 forwardMostMove = boardIndex;
9134             }
9135             return;
9136           case ElapsedTime:
9137             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9138                 strcat(parseList[boardIndex-1], " ");
9139                 strcat(parseList[boardIndex-1], yy_text);
9140             }
9141             continue;
9142           case Comment:
9143           case PGNTag:
9144           case NAG:
9145           default:
9146             /* ignore */
9147             continue;
9148           case WhiteWins:
9149           case BlackWins:
9150           case GameIsDrawn:
9151           case GameUnfinished:
9152             if (gameMode == IcsExamining) {
9153                 if (boardIndex < backwardMostMove) {
9154                     /* Oops, gap.  How did that happen? */
9155                     return;
9156                 }
9157                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9158                 return;
9159             }
9160             gameInfo.result = moveType;
9161             p = strchr(yy_text, '{');
9162             if (p == NULL) p = strchr(yy_text, '(');
9163             if (p == NULL) {
9164                 p = yy_text;
9165                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9166             } else {
9167                 q = strchr(p, *p == '{' ? '}' : ')');
9168                 if (q != NULL) *q = NULLCHAR;
9169                 p++;
9170             }
9171             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9172             gameInfo.resultDetails = StrSave(p);
9173             continue;
9174         }
9175         if (boardIndex >= forwardMostMove &&
9176             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9177             backwardMostMove = blackPlaysFirst ? 1 : 0;
9178             return;
9179         }
9180         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9181                                  fromY, fromX, toY, toX, promoChar,
9182                                  parseList[boardIndex]);
9183         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9184         /* currentMoveString is set as a side-effect of yylex */
9185         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9186         strcat(moveList[boardIndex], "\n");
9187         boardIndex++;
9188         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9189         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9190           case MT_NONE:
9191           case MT_STALEMATE:
9192           default:
9193             break;
9194           case MT_CHECK:
9195             if(gameInfo.variant != VariantShogi)
9196                 strcat(parseList[boardIndex - 1], "+");
9197             break;
9198           case MT_CHECKMATE:
9199           case MT_STAINMATE:
9200             strcat(parseList[boardIndex - 1], "#");
9201             break;
9202         }
9203     }
9204 }
9205
9206
9207 /* Apply a move to the given board  */
9208 void
9209 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9210 {
9211   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9212   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9213
9214     /* [HGM] compute & store e.p. status and castling rights for new position */
9215     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9216
9217       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9218       oldEP = (signed char)board[EP_STATUS];
9219       board[EP_STATUS] = EP_NONE;
9220
9221   if (fromY == DROP_RANK) {
9222         /* must be first */
9223         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9224             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9225             return;
9226         }
9227         piece = board[toY][toX] = (ChessSquare) fromX;
9228   } else {
9229       int i;
9230
9231       if( board[toY][toX] != EmptySquare )
9232            board[EP_STATUS] = EP_CAPTURE;
9233
9234       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9235            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9236                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9237       } else
9238       if( board[fromY][fromX] == WhitePawn ) {
9239            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9240                board[EP_STATUS] = EP_PAWN_MOVE;
9241            if( toY-fromY==2) {
9242                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9243                         gameInfo.variant != VariantBerolina || toX < fromX)
9244                       board[EP_STATUS] = toX | berolina;
9245                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9246                         gameInfo.variant != VariantBerolina || toX > fromX)
9247                       board[EP_STATUS] = toX;
9248            }
9249       } else
9250       if( board[fromY][fromX] == BlackPawn ) {
9251            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9252                board[EP_STATUS] = EP_PAWN_MOVE;
9253            if( toY-fromY== -2) {
9254                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9255                         gameInfo.variant != VariantBerolina || toX < fromX)
9256                       board[EP_STATUS] = toX | berolina;
9257                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9258                         gameInfo.variant != VariantBerolina || toX > fromX)
9259                       board[EP_STATUS] = toX;
9260            }
9261        }
9262
9263        for(i=0; i<nrCastlingRights; i++) {
9264            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9265               board[CASTLING][i] == toX   && castlingRank[i] == toY
9266              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9267        }
9268
9269      if (fromX == toX && fromY == toY) return;
9270
9271      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9272      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9273      if(gameInfo.variant == VariantKnightmate)
9274          king += (int) WhiteUnicorn - (int) WhiteKing;
9275
9276     /* Code added by Tord: */
9277     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9278     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9279         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9280       board[fromY][fromX] = EmptySquare;
9281       board[toY][toX] = EmptySquare;
9282       if((toX > fromX) != (piece == WhiteRook)) {
9283         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9284       } else {
9285         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9286       }
9287     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9288                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9289       board[fromY][fromX] = EmptySquare;
9290       board[toY][toX] = EmptySquare;
9291       if((toX > fromX) != (piece == BlackRook)) {
9292         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9293       } else {
9294         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9295       }
9296     /* End of code added by Tord */
9297
9298     } else if (board[fromY][fromX] == king
9299         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9300         && toY == fromY && toX > fromX+1) {
9301         board[fromY][fromX] = EmptySquare;
9302         board[toY][toX] = king;
9303         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9304         board[fromY][BOARD_RGHT-1] = EmptySquare;
9305     } else if (board[fromY][fromX] == king
9306         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9307                && toY == fromY && toX < fromX-1) {
9308         board[fromY][fromX] = EmptySquare;
9309         board[toY][toX] = king;
9310         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9311         board[fromY][BOARD_LEFT] = EmptySquare;
9312     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9313                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9314                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9315                ) {
9316         /* white pawn promotion */
9317         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9318         if(gameInfo.variant==VariantBughouse ||
9319            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9320             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9321         board[fromY][fromX] = EmptySquare;
9322     } else if ((fromY >= BOARD_HEIGHT>>1)
9323                && (toX != fromX)
9324                && gameInfo.variant != VariantXiangqi
9325                && gameInfo.variant != VariantBerolina
9326                && (board[fromY][fromX] == WhitePawn)
9327                && (board[toY][toX] == EmptySquare)) {
9328         board[fromY][fromX] = EmptySquare;
9329         board[toY][toX] = WhitePawn;
9330         captured = board[toY - 1][toX];
9331         board[toY - 1][toX] = EmptySquare;
9332     } else if ((fromY == BOARD_HEIGHT-4)
9333                && (toX == fromX)
9334                && gameInfo.variant == VariantBerolina
9335                && (board[fromY][fromX] == WhitePawn)
9336                && (board[toY][toX] == EmptySquare)) {
9337         board[fromY][fromX] = EmptySquare;
9338         board[toY][toX] = WhitePawn;
9339         if(oldEP & EP_BEROLIN_A) {
9340                 captured = board[fromY][fromX-1];
9341                 board[fromY][fromX-1] = EmptySquare;
9342         }else{  captured = board[fromY][fromX+1];
9343                 board[fromY][fromX+1] = EmptySquare;
9344         }
9345     } else if (board[fromY][fromX] == king
9346         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9347                && toY == fromY && toX > fromX+1) {
9348         board[fromY][fromX] = EmptySquare;
9349         board[toY][toX] = king;
9350         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9351         board[fromY][BOARD_RGHT-1] = EmptySquare;
9352     } else if (board[fromY][fromX] == king
9353         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9354                && toY == fromY && toX < fromX-1) {
9355         board[fromY][fromX] = EmptySquare;
9356         board[toY][toX] = king;
9357         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9358         board[fromY][BOARD_LEFT] = EmptySquare;
9359     } else if (fromY == 7 && fromX == 3
9360                && board[fromY][fromX] == BlackKing
9361                && toY == 7 && toX == 5) {
9362         board[fromY][fromX] = EmptySquare;
9363         board[toY][toX] = BlackKing;
9364         board[fromY][7] = EmptySquare;
9365         board[toY][4] = BlackRook;
9366     } else if (fromY == 7 && fromX == 3
9367                && board[fromY][fromX] == BlackKing
9368                && toY == 7 && toX == 1) {
9369         board[fromY][fromX] = EmptySquare;
9370         board[toY][toX] = BlackKing;
9371         board[fromY][0] = EmptySquare;
9372         board[toY][2] = BlackRook;
9373     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9374                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9375                && toY < promoRank && promoChar
9376                ) {
9377         /* black pawn promotion */
9378         board[toY][toX] = CharToPiece(ToLower(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] == BlackPawn)
9388                && (board[toY][toX] == EmptySquare)) {
9389         board[fromY][fromX] = EmptySquare;
9390         board[toY][toX] = BlackPawn;
9391         captured = board[toY + 1][toX];
9392         board[toY + 1][toX] = EmptySquare;
9393     } else if ((fromY == 3)
9394                && (toX == fromX)
9395                && gameInfo.variant == VariantBerolina
9396                && (board[fromY][fromX] == BlackPawn)
9397                && (board[toY][toX] == EmptySquare)) {
9398         board[fromY][fromX] = EmptySquare;
9399         board[toY][toX] = BlackPawn;
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 {
9407         board[toY][toX] = board[fromY][fromX];
9408         board[fromY][fromX] = EmptySquare;
9409     }
9410   }
9411
9412     if (gameInfo.holdingsWidth != 0) {
9413
9414       /* !!A lot more code needs to be written to support holdings  */
9415       /* [HGM] OK, so I have written it. Holdings are stored in the */
9416       /* penultimate board files, so they are automaticlly stored   */
9417       /* in the game history.                                       */
9418       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9419                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9420         /* Delete from holdings, by decreasing count */
9421         /* and erasing image if necessary            */
9422         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9423         if(p < (int) BlackPawn) { /* white drop */
9424              p -= (int)WhitePawn;
9425                  p = PieceToNumber((ChessSquare)p);
9426              if(p >= gameInfo.holdingsSize) p = 0;
9427              if(--board[p][BOARD_WIDTH-2] <= 0)
9428                   board[p][BOARD_WIDTH-1] = EmptySquare;
9429              if((int)board[p][BOARD_WIDTH-2] < 0)
9430                         board[p][BOARD_WIDTH-2] = 0;
9431         } else {                  /* black drop */
9432              p -= (int)BlackPawn;
9433                  p = PieceToNumber((ChessSquare)p);
9434              if(p >= gameInfo.holdingsSize) p = 0;
9435              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9436                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9437              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9438                         board[BOARD_HEIGHT-1-p][1] = 0;
9439         }
9440       }
9441       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9442           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9443         /* [HGM] holdings: Add to holdings, if holdings exist */
9444         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9445                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9446                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9447         }
9448         p = (int) captured;
9449         if (p >= (int) BlackPawn) {
9450           p -= (int)BlackPawn;
9451           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9452                   /* in Shogi restore piece to its original  first */
9453                   captured = (ChessSquare) (DEMOTED captured);
9454                   p = DEMOTED p;
9455           }
9456           p = PieceToNumber((ChessSquare)p);
9457           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9458           board[p][BOARD_WIDTH-2]++;
9459           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9460         } else {
9461           p -= (int)WhitePawn;
9462           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9463                   captured = (ChessSquare) (DEMOTED captured);
9464                   p = DEMOTED p;
9465           }
9466           p = PieceToNumber((ChessSquare)p);
9467           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9468           board[BOARD_HEIGHT-1-p][1]++;
9469           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9470         }
9471       }
9472     } else if (gameInfo.variant == VariantAtomic) {
9473       if (captured != EmptySquare) {
9474         int y, x;
9475         for (y = toY-1; y <= toY+1; y++) {
9476           for (x = toX-1; x <= toX+1; x++) {
9477             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9478                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9479               board[y][x] = EmptySquare;
9480             }
9481           }
9482         }
9483         board[toY][toX] = EmptySquare;
9484       }
9485     }
9486     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9487         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9488     } else
9489     if(promoChar == '+') {
9490         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9491         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9492     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9493         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9494         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9495            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9496         board[toY][toX] = newPiece;
9497     }
9498     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9499                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9500         // [HGM] superchess: take promotion piece out of holdings
9501         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9502         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9503             if(!--board[k][BOARD_WIDTH-2])
9504                 board[k][BOARD_WIDTH-1] = EmptySquare;
9505         } else {
9506             if(!--board[BOARD_HEIGHT-1-k][1])
9507                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9508         }
9509     }
9510
9511 }
9512
9513 /* Updates forwardMostMove */
9514 void
9515 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9516 {
9517 //    forwardMostMove++; // [HGM] bare: moved downstream
9518
9519     (void) CoordsToAlgebraic(boards[forwardMostMove],
9520                              PosFlags(forwardMostMove),
9521                              fromY, fromX, toY, toX, promoChar,
9522                              parseList[forwardMostMove]);
9523
9524     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9525         int timeLeft; static int lastLoadFlag=0; int king, piece;
9526         piece = boards[forwardMostMove][fromY][fromX];
9527         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9528         if(gameInfo.variant == VariantKnightmate)
9529             king += (int) WhiteUnicorn - (int) WhiteKing;
9530         if(forwardMostMove == 0) {
9531             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9532                 fprintf(serverMoves, "%s;", UserName());
9533             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9534                 fprintf(serverMoves, "%s;", second.tidy);
9535             fprintf(serverMoves, "%s;", first.tidy);
9536             if(gameMode == MachinePlaysWhite)
9537                 fprintf(serverMoves, "%s;", UserName());
9538             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9539                 fprintf(serverMoves, "%s;", second.tidy);
9540         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9541         lastLoadFlag = loadFlag;
9542         // print base move
9543         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9544         // print castling suffix
9545         if( toY == fromY && piece == king ) {
9546             if(toX-fromX > 1)
9547                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9548             if(fromX-toX >1)
9549                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9550         }
9551         // e.p. suffix
9552         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9553              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9554              boards[forwardMostMove][toY][toX] == EmptySquare
9555              && fromX != toX && fromY != toY)
9556                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9557         // promotion suffix
9558         if(promoChar != NULLCHAR)
9559                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9560         if(!loadFlag) {
9561                 char buf[MOVE_LEN*2], *p; int len;
9562             fprintf(serverMoves, "/%d/%d",
9563                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9564             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9565             else                      timeLeft = blackTimeRemaining/1000;
9566             fprintf(serverMoves, "/%d", timeLeft);
9567                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9568                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9569                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9570             fprintf(serverMoves, "/%s", buf);
9571         }
9572         fflush(serverMoves);
9573     }
9574
9575     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9576         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9577       return;
9578     }
9579     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9580     if (commentList[forwardMostMove+1] != NULL) {
9581         free(commentList[forwardMostMove+1]);
9582         commentList[forwardMostMove+1] = NULL;
9583     }
9584     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9585     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9586     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9587     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9588     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9589     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9590     adjustedClock = FALSE;
9591     gameInfo.result = GameUnfinished;
9592     if (gameInfo.resultDetails != NULL) {
9593         free(gameInfo.resultDetails);
9594         gameInfo.resultDetails = NULL;
9595     }
9596     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9597                               moveList[forwardMostMove - 1]);
9598     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9599       case MT_NONE:
9600       case MT_STALEMATE:
9601       default:
9602         break;
9603       case MT_CHECK:
9604         if(gameInfo.variant != VariantShogi)
9605             strcat(parseList[forwardMostMove - 1], "+");
9606         break;
9607       case MT_CHECKMATE:
9608       case MT_STAINMATE:
9609         strcat(parseList[forwardMostMove - 1], "#");
9610         break;
9611     }
9612
9613 }
9614
9615 /* Updates currentMove if not pausing */
9616 void
9617 ShowMove (int fromX, int fromY, int toX, int toY)
9618 {
9619     int instant = (gameMode == PlayFromGameFile) ?
9620         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9621     if(appData.noGUI) return;
9622     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9623         if (!instant) {
9624             if (forwardMostMove == currentMove + 1) {
9625                 AnimateMove(boards[forwardMostMove - 1],
9626                             fromX, fromY, toX, toY);
9627             }
9628             if (appData.highlightLastMove) {
9629                 SetHighlights(fromX, fromY, toX, toY);
9630             }
9631         }
9632         currentMove = forwardMostMove;
9633     }
9634
9635     if (instant) return;
9636
9637     DisplayMove(currentMove - 1);
9638     DrawPosition(FALSE, boards[currentMove]);
9639     DisplayBothClocks();
9640     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9641 }
9642
9643 void
9644 SendEgtPath (ChessProgramState *cps)
9645 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9646         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9647
9648         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9649
9650         while(*p) {
9651             char c, *q = name+1, *r, *s;
9652
9653             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9654             while(*p && *p != ',') *q++ = *p++;
9655             *q++ = ':'; *q = 0;
9656             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9657                 strcmp(name, ",nalimov:") == 0 ) {
9658                 // take nalimov path from the menu-changeable option first, if it is defined
9659               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9660                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9661             } else
9662             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9663                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9664                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9665                 s = r = StrStr(s, ":") + 1; // beginning of path info
9666                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9667                 c = *r; *r = 0;             // temporarily null-terminate path info
9668                     *--q = 0;               // strip of trailig ':' from name
9669                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9670                 *r = c;
9671                 SendToProgram(buf,cps);     // send egtbpath command for this format
9672             }
9673             if(*p == ',') p++; // read away comma to position for next format name
9674         }
9675 }
9676
9677 void
9678 InitChessProgram (ChessProgramState *cps, int setup)
9679 /* setup needed to setup FRC opening position */
9680 {
9681     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9682     if (appData.noChessProgram) return;
9683     hintRequested = FALSE;
9684     bookRequested = FALSE;
9685
9686     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9687     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9688     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9689     if(cps->memSize) { /* [HGM] memory */
9690       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9691         SendToProgram(buf, cps);
9692     }
9693     SendEgtPath(cps); /* [HGM] EGT */
9694     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9695       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9696         SendToProgram(buf, cps);
9697     }
9698
9699     SendToProgram(cps->initString, cps);
9700     if (gameInfo.variant != VariantNormal &&
9701         gameInfo.variant != VariantLoadable
9702         /* [HGM] also send variant if board size non-standard */
9703         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9704                                             ) {
9705       char *v = VariantName(gameInfo.variant);
9706       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9707         /* [HGM] in protocol 1 we have to assume all variants valid */
9708         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9709         DisplayFatalError(buf, 0, 1);
9710         return;
9711       }
9712
9713       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9714       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9715       if( gameInfo.variant == VariantXiangqi )
9716            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9717       if( gameInfo.variant == VariantShogi )
9718            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9719       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9720            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9721       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9722           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9723            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9724       if( gameInfo.variant == VariantCourier )
9725            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9726       if( gameInfo.variant == VariantSuper )
9727            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9728       if( gameInfo.variant == VariantGreat )
9729            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9730       if( gameInfo.variant == VariantSChess )
9731            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9732       if( gameInfo.variant == VariantGrand )
9733            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9734
9735       if(overruled) {
9736         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9737                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9738            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9739            if(StrStr(cps->variants, b) == NULL) {
9740                // specific sized variant not known, check if general sizing allowed
9741                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9742                    if(StrStr(cps->variants, "boardsize") == NULL) {
9743                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9744                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9745                        DisplayFatalError(buf, 0, 1);
9746                        return;
9747                    }
9748                    /* [HGM] here we really should compare with the maximum supported board size */
9749                }
9750            }
9751       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9752       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9753       SendToProgram(buf, cps);
9754     }
9755     currentlyInitializedVariant = gameInfo.variant;
9756
9757     /* [HGM] send opening position in FRC to first engine */
9758     if(setup) {
9759           SendToProgram("force\n", cps);
9760           SendBoard(cps, 0);
9761           /* engine is now in force mode! Set flag to wake it up after first move. */
9762           setboardSpoiledMachineBlack = 1;
9763     }
9764
9765     if (cps->sendICS) {
9766       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9767       SendToProgram(buf, cps);
9768     }
9769     cps->maybeThinking = FALSE;
9770     cps->offeredDraw = 0;
9771     if (!appData.icsActive) {
9772         SendTimeControl(cps, movesPerSession, timeControl,
9773                         timeIncrement, appData.searchDepth,
9774                         searchTime);
9775     }
9776     if (appData.showThinking
9777         // [HGM] thinking: four options require thinking output to be sent
9778         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9779                                 ) {
9780         SendToProgram("post\n", cps);
9781     }
9782     SendToProgram("hard\n", cps);
9783     if (!appData.ponderNextMove) {
9784         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9785            it without being sure what state we are in first.  "hard"
9786            is not a toggle, so that one is OK.
9787          */
9788         SendToProgram("easy\n", cps);
9789     }
9790     if (cps->usePing) {
9791       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9792       SendToProgram(buf, cps);
9793     }
9794     cps->initDone = TRUE;
9795     ClearEngineOutputPane(cps == &second);
9796 }
9797
9798
9799 void
9800 StartChessProgram (ChessProgramState *cps)
9801 {
9802     char buf[MSG_SIZ];
9803     int err;
9804
9805     if (appData.noChessProgram) return;
9806     cps->initDone = FALSE;
9807
9808     if (strcmp(cps->host, "localhost") == 0) {
9809         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9810     } else if (*appData.remoteShell == NULLCHAR) {
9811         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9812     } else {
9813         if (*appData.remoteUser == NULLCHAR) {
9814           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9815                     cps->program);
9816         } else {
9817           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9818                     cps->host, appData.remoteUser, cps->program);
9819         }
9820         err = StartChildProcess(buf, "", &cps->pr);
9821     }
9822
9823     if (err != 0) {
9824       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9825         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9826         if(cps != &first) return;
9827         appData.noChessProgram = TRUE;
9828         ThawUI();
9829         SetNCPMode();
9830 //      DisplayFatalError(buf, err, 1);
9831 //      cps->pr = NoProc;
9832 //      cps->isr = NULL;
9833         return;
9834     }
9835
9836     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9837     if (cps->protocolVersion > 1) {
9838       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9839       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9840       cps->comboCnt = 0;  //                and values of combo boxes
9841       SendToProgram(buf, cps);
9842     } else {
9843       SendToProgram("xboard\n", cps);
9844     }
9845 }
9846
9847 void
9848 TwoMachinesEventIfReady P((void))
9849 {
9850   static int curMess = 0;
9851   if (first.lastPing != first.lastPong) {
9852     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9853     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9854     return;
9855   }
9856   if (second.lastPing != second.lastPong) {
9857     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9858     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9859     return;
9860   }
9861   DisplayMessage("", ""); curMess = 0;
9862   ThawUI();
9863   TwoMachinesEvent();
9864 }
9865
9866 char *
9867 MakeName (char *template)
9868 {
9869     time_t clock;
9870     struct tm *tm;
9871     static char buf[MSG_SIZ];
9872     char *p = buf;
9873     int i;
9874
9875     clock = time((time_t *)NULL);
9876     tm = localtime(&clock);
9877
9878     while(*p++ = *template++) if(p[-1] == '%') {
9879         switch(*template++) {
9880           case 0:   *p = 0; return buf;
9881           case 'Y': i = tm->tm_year+1900; break;
9882           case 'y': i = tm->tm_year-100; break;
9883           case 'M': i = tm->tm_mon+1; break;
9884           case 'd': i = tm->tm_mday; break;
9885           case 'h': i = tm->tm_hour; break;
9886           case 'm': i = tm->tm_min; break;
9887           case 's': i = tm->tm_sec; break;
9888           default:  i = 0;
9889         }
9890         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9891     }
9892     return buf;
9893 }
9894
9895 int
9896 CountPlayers (char *p)
9897 {
9898     int n = 0;
9899     while(p = strchr(p, '\n')) p++, n++; // count participants
9900     return n;
9901 }
9902
9903 FILE *
9904 WriteTourneyFile (char *results, FILE *f)
9905 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9906     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9907     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9908         // create a file with tournament description
9909         fprintf(f, "-participants {%s}\n", appData.participants);
9910         fprintf(f, "-seedBase %d\n", appData.seedBase);
9911         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9912         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9913         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9914         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9915         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9916         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9917         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9918         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9919         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9920         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9921         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9922         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9923         if(searchTime > 0)
9924                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9925         else {
9926                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9927                 fprintf(f, "-tc %s\n", appData.timeControl);
9928                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9929         }
9930         fprintf(f, "-results \"%s\"\n", results);
9931     }
9932     return f;
9933 }
9934
9935 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9936
9937 void
9938 Substitute (char *participants, int expunge)
9939 {
9940     int i, changed, changes=0, nPlayers=0;
9941     char *p, *q, *r, buf[MSG_SIZ];
9942     if(participants == NULL) return;
9943     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9944     r = p = participants; q = appData.participants;
9945     while(*p && *p == *q) {
9946         if(*p == '\n') r = p+1, nPlayers++;
9947         p++; q++;
9948     }
9949     if(*p) { // difference
9950         while(*p && *p++ != '\n');
9951         while(*q && *q++ != '\n');
9952       changed = nPlayers;
9953         changes = 1 + (strcmp(p, q) != 0);
9954     }
9955     if(changes == 1) { // a single engine mnemonic was changed
9956         q = r; while(*q) nPlayers += (*q++ == '\n');
9957         p = buf; while(*r && (*p = *r++) != '\n') p++;
9958         *p = NULLCHAR;
9959         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9960         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9961         if(mnemonic[i]) { // The substitute is valid
9962             FILE *f;
9963             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9964                 flock(fileno(f), LOCK_EX);
9965                 ParseArgsFromFile(f);
9966                 fseek(f, 0, SEEK_SET);
9967                 FREE(appData.participants); appData.participants = participants;
9968                 if(expunge) { // erase results of replaced engine
9969                     int len = strlen(appData.results), w, b, dummy;
9970                     for(i=0; i<len; i++) {
9971                         Pairing(i, nPlayers, &w, &b, &dummy);
9972                         if((w == changed || b == changed) && appData.results[i] == '*') {
9973                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9974                             fclose(f);
9975                             return;
9976                         }
9977                     }
9978                     for(i=0; i<len; i++) {
9979                         Pairing(i, nPlayers, &w, &b, &dummy);
9980                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9981                     }
9982                 }
9983                 WriteTourneyFile(appData.results, f);
9984                 fclose(f); // release lock
9985                 return;
9986             }
9987         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9988     }
9989     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9990     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9991     free(participants);
9992     return;
9993 }
9994
9995 int
9996 CreateTourney (char *name)
9997 {
9998         FILE *f;
9999         if(matchMode && strcmp(name, appData.tourneyFile)) {
10000              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10001         }
10002         if(name[0] == NULLCHAR) {
10003             if(appData.participants[0])
10004                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10005             return 0;
10006         }
10007         f = fopen(name, "r");
10008         if(f) { // file exists
10009             ASSIGN(appData.tourneyFile, name);
10010             ParseArgsFromFile(f); // parse it
10011         } else {
10012             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10013             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10014                 DisplayError(_("Not enough participants"), 0);
10015                 return 0;
10016             }
10017             ASSIGN(appData.tourneyFile, name);
10018             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10019             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10020         }
10021         fclose(f);
10022         appData.noChessProgram = FALSE;
10023         appData.clockMode = TRUE;
10024         SetGNUMode();
10025         return 1;
10026 }
10027
10028 int
10029 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10030 {
10031     char buf[MSG_SIZ], *p, *q;
10032     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10033     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10034     skip = !all && group[0]; // if group requested, we start in skip mode
10035     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10036         p = names; q = buf; header = 0;
10037         while(*p && *p != '\n') *q++ = *p++;
10038         *q = 0;
10039         if(*p == '\n') p++;
10040         if(buf[0] == '#') {
10041             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10042             depth++; // we must be entering a new group
10043             if(all) continue; // suppress printing group headers when complete list requested
10044             header = 1;
10045             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10046         }
10047         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10048         if(engineList[i]) free(engineList[i]);
10049         engineList[i] = strdup(buf);
10050         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10051         if(engineMnemonic[i]) free(engineMnemonic[i]);
10052         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10053             strcat(buf, " (");
10054             sscanf(q + 8, "%s", buf + strlen(buf));
10055             strcat(buf, ")");
10056         }
10057         engineMnemonic[i] = strdup(buf);
10058         i++;
10059     }
10060     engineList[i] = engineMnemonic[i] = NULL;
10061     return i;
10062 }
10063
10064 // following implemented as macro to avoid type limitations
10065 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10066
10067 void
10068 SwapEngines (int n)
10069 {   // swap settings for first engine and other engine (so far only some selected options)
10070     int h;
10071     char *p;
10072     if(n == 0) return;
10073     SWAP(directory, p)
10074     SWAP(chessProgram, p)
10075     SWAP(isUCI, h)
10076     SWAP(hasOwnBookUCI, h)
10077     SWAP(protocolVersion, h)
10078     SWAP(reuse, h)
10079     SWAP(scoreIsAbsolute, h)
10080     SWAP(timeOdds, h)
10081     SWAP(logo, p)
10082     SWAP(pgnName, p)
10083     SWAP(pvSAN, h)
10084     SWAP(engOptions, p)
10085     SWAP(engInitString, p)
10086     SWAP(computerString, p)
10087     SWAP(features, p)
10088     SWAP(fenOverride, p)
10089     SWAP(NPS, h)
10090     SWAP(accumulateTC, h)
10091     SWAP(host, p)
10092 }
10093
10094 int
10095 SetPlayer (int player, char *p)
10096 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10097     int i;
10098     char buf[MSG_SIZ], *engineName;
10099     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10100     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10101     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10102     if(mnemonic[i]) {
10103         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10104         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10105         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10106         ParseArgsFromString(buf);
10107     }
10108     free(engineName);
10109     return i;
10110 }
10111
10112 char *recentEngines;
10113
10114 void
10115 RecentEngineEvent (int nr)
10116 {
10117     int n;
10118 //    SwapEngines(1); // bump first to second
10119 //    ReplaceEngine(&second, 1); // and load it there
10120     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10121     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10122     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10123         ReplaceEngine(&first, 0);
10124         FloatToFront(&appData.recentEngineList, command[n]);
10125     }
10126 }
10127
10128 int
10129 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10130 {   // determine players from game number
10131     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10132
10133     if(appData.tourneyType == 0) {
10134         roundsPerCycle = (nPlayers - 1) | 1;
10135         pairingsPerRound = nPlayers / 2;
10136     } else if(appData.tourneyType > 0) {
10137         roundsPerCycle = nPlayers - appData.tourneyType;
10138         pairingsPerRound = appData.tourneyType;
10139     }
10140     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10141     gamesPerCycle = gamesPerRound * roundsPerCycle;
10142     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10143     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10144     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10145     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10146     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10147     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10148
10149     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10150     if(appData.roundSync) *syncInterval = gamesPerRound;
10151
10152     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10153
10154     if(appData.tourneyType == 0) {
10155         if(curPairing == (nPlayers-1)/2 ) {
10156             *whitePlayer = curRound;
10157             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10158         } else {
10159             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10160             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10161             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10162             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10163         }
10164     } else if(appData.tourneyType > 1) {
10165         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10166         *whitePlayer = curRound + appData.tourneyType;
10167     } else if(appData.tourneyType > 0) {
10168         *whitePlayer = curPairing;
10169         *blackPlayer = curRound + appData.tourneyType;
10170     }
10171
10172     // take care of white/black alternation per round. 
10173     // For cycles and games this is already taken care of by default, derived from matchGame!
10174     return curRound & 1;
10175 }
10176
10177 int
10178 NextTourneyGame (int nr, int *swapColors)
10179 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10180     char *p, *q;
10181     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10182     FILE *tf;
10183     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10184     tf = fopen(appData.tourneyFile, "r");
10185     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10186     ParseArgsFromFile(tf); fclose(tf);
10187     InitTimeControls(); // TC might be altered from tourney file
10188
10189     nPlayers = CountPlayers(appData.participants); // count participants
10190     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10191     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10192
10193     if(syncInterval) {
10194         p = q = appData.results;
10195         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10196         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10197             DisplayMessage(_("Waiting for other game(s)"),"");
10198             waitingForGame = TRUE;
10199             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10200             return 0;
10201         }
10202         waitingForGame = FALSE;
10203     }
10204
10205     if(appData.tourneyType < 0) {
10206         if(nr>=0 && !pairingReceived) {
10207             char buf[1<<16];
10208             if(pairing.pr == NoProc) {
10209                 if(!appData.pairingEngine[0]) {
10210                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10211                     return 0;
10212                 }
10213                 StartChessProgram(&pairing); // starts the pairing engine
10214             }
10215             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10216             SendToProgram(buf, &pairing);
10217             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10218             SendToProgram(buf, &pairing);
10219             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10220         }
10221         pairingReceived = 0;                              // ... so we continue here 
10222         *swapColors = 0;
10223         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10224         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10225         matchGame = 1; roundNr = nr / syncInterval + 1;
10226     }
10227
10228     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10229
10230     // redefine engines, engine dir, etc.
10231     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10232     if(first.pr == NoProc) {
10233       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10234       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10235     }
10236     if(second.pr == NoProc) {
10237       SwapEngines(1);
10238       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10239       SwapEngines(1);         // and make that valid for second engine by swapping
10240       InitEngine(&second, 1);
10241     }
10242     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10243     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10244     return 1;
10245 }
10246
10247 void
10248 NextMatchGame ()
10249 {   // performs game initialization that does not invoke engines, and then tries to start the game
10250     int res, firstWhite, swapColors = 0;
10251     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10252     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
10253         char buf[MSG_SIZ];
10254         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10255         if(strcmp(buf, currentDebugFile)) { // name has changed
10256             FILE *f = fopen(buf, "w");
10257             if(f) { // if opening the new file failed, just keep using the old one
10258                 ASSIGN(currentDebugFile, buf);
10259                 fclose(debugFP);
10260                 debugFP = f;
10261             }
10262             if(appData.serverFileName) {
10263                 if(serverFP) fclose(serverFP);
10264                 serverFP = fopen(appData.serverFileName, "w");
10265                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10266                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10267             }
10268         }
10269     }
10270     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10271     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10272     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10273     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10274     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10275     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10276     Reset(FALSE, first.pr != NoProc);
10277     res = LoadGameOrPosition(matchGame); // setup game
10278     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10279     if(!res) return; // abort when bad game/pos file
10280     TwoMachinesEvent();
10281 }
10282
10283 void
10284 UserAdjudicationEvent (int result)
10285 {
10286     ChessMove gameResult = GameIsDrawn;
10287
10288     if( result > 0 ) {
10289         gameResult = WhiteWins;
10290     }
10291     else if( result < 0 ) {
10292         gameResult = BlackWins;
10293     }
10294
10295     if( gameMode == TwoMachinesPlay ) {
10296         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10297     }
10298 }
10299
10300
10301 // [HGM] save: calculate checksum of game to make games easily identifiable
10302 int
10303 StringCheckSum (char *s)
10304 {
10305         int i = 0;
10306         if(s==NULL) return 0;
10307         while(*s) i = i*259 + *s++;
10308         return i;
10309 }
10310
10311 int
10312 GameCheckSum ()
10313 {
10314         int i, sum=0;
10315         for(i=backwardMostMove; i<forwardMostMove; i++) {
10316                 sum += pvInfoList[i].depth;
10317                 sum += StringCheckSum(parseList[i]);
10318                 sum += StringCheckSum(commentList[i]);
10319                 sum *= 261;
10320         }
10321         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10322         return sum + StringCheckSum(commentList[i]);
10323 } // end of save patch
10324
10325 void
10326 GameEnds (ChessMove result, char *resultDetails, int whosays)
10327 {
10328     GameMode nextGameMode;
10329     int isIcsGame;
10330     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10331
10332     if(endingGame) return; /* [HGM] crash: forbid recursion */
10333     endingGame = 1;
10334     if(twoBoards) { // [HGM] dual: switch back to one board
10335         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10336         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10337     }
10338     if (appData.debugMode) {
10339       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10340               result, resultDetails ? resultDetails : "(null)", whosays);
10341     }
10342
10343     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10344
10345     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10346         /* If we are playing on ICS, the server decides when the
10347            game is over, but the engine can offer to draw, claim
10348            a draw, or resign.
10349          */
10350 #if ZIPPY
10351         if (appData.zippyPlay && first.initDone) {
10352             if (result == GameIsDrawn) {
10353                 /* In case draw still needs to be claimed */
10354                 SendToICS(ics_prefix);
10355                 SendToICS("draw\n");
10356             } else if (StrCaseStr(resultDetails, "resign")) {
10357                 SendToICS(ics_prefix);
10358                 SendToICS("resign\n");
10359             }
10360         }
10361 #endif
10362         endingGame = 0; /* [HGM] crash */
10363         return;
10364     }
10365
10366     /* If we're loading the game from a file, stop */
10367     if (whosays == GE_FILE) {
10368       (void) StopLoadGameTimer();
10369       gameFileFP = NULL;
10370     }
10371
10372     /* Cancel draw offers */
10373     first.offeredDraw = second.offeredDraw = 0;
10374
10375     /* If this is an ICS game, only ICS can really say it's done;
10376        if not, anyone can. */
10377     isIcsGame = (gameMode == IcsPlayingWhite ||
10378                  gameMode == IcsPlayingBlack ||
10379                  gameMode == IcsObserving    ||
10380                  gameMode == IcsExamining);
10381
10382     if (!isIcsGame || whosays == GE_ICS) {
10383         /* OK -- not an ICS game, or ICS said it was done */
10384         StopClocks();
10385         if (!isIcsGame && !appData.noChessProgram)
10386           SetUserThinkingEnables();
10387
10388         /* [HGM] if a machine claims the game end we verify this claim */
10389         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10390             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10391                 char claimer;
10392                 ChessMove trueResult = (ChessMove) -1;
10393
10394                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10395                                             first.twoMachinesColor[0] :
10396                                             second.twoMachinesColor[0] ;
10397
10398                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10399                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10400                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10401                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10402                 } else
10403                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10404                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10405                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10406                 } else
10407                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10408                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10409                 }
10410
10411                 // now verify win claims, but not in drop games, as we don't understand those yet
10412                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10413                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10414                     (result == WhiteWins && claimer == 'w' ||
10415                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10416                       if (appData.debugMode) {
10417                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10418                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10419                       }
10420                       if(result != trueResult) {
10421                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10422                               result = claimer == 'w' ? BlackWins : WhiteWins;
10423                               resultDetails = buf;
10424                       }
10425                 } else
10426                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10427                     && (forwardMostMove <= backwardMostMove ||
10428                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10429                         (claimer=='b')==(forwardMostMove&1))
10430                                                                                   ) {
10431                       /* [HGM] verify: draws that were not flagged are false claims */
10432                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10433                       result = claimer == 'w' ? BlackWins : WhiteWins;
10434                       resultDetails = buf;
10435                 }
10436                 /* (Claiming a loss is accepted no questions asked!) */
10437             }
10438             /* [HGM] bare: don't allow bare King to win */
10439             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10440                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10441                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10442                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10443                && result != GameIsDrawn)
10444             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10445                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10446                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10447                         if(p >= 0 && p <= (int)WhiteKing) k++;
10448                 }
10449                 if (appData.debugMode) {
10450                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10451                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10452                 }
10453                 if(k <= 1) {
10454                         result = GameIsDrawn;
10455                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10456                         resultDetails = buf;
10457                 }
10458             }
10459         }
10460
10461
10462         if(serverMoves != NULL && !loadFlag) { char c = '=';
10463             if(result==WhiteWins) c = '+';
10464             if(result==BlackWins) c = '-';
10465             if(resultDetails != NULL)
10466                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10467         }
10468         if (resultDetails != NULL) {
10469             gameInfo.result = result;
10470             gameInfo.resultDetails = StrSave(resultDetails);
10471
10472             /* display last move only if game was not loaded from file */
10473             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10474                 DisplayMove(currentMove - 1);
10475
10476             if (forwardMostMove != 0) {
10477                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10478                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10479                                                                 ) {
10480                     if (*appData.saveGameFile != NULLCHAR) {
10481                         SaveGameToFile(appData.saveGameFile, TRUE);
10482                     } else if (appData.autoSaveGames) {
10483                         AutoSaveGame();
10484                     }
10485                     if (*appData.savePositionFile != NULLCHAR) {
10486                         SavePositionToFile(appData.savePositionFile);
10487                     }
10488                 }
10489             }
10490
10491             /* Tell program how game ended in case it is learning */
10492             /* [HGM] Moved this to after saving the PGN, just in case */
10493             /* engine died and we got here through time loss. In that */
10494             /* case we will get a fatal error writing the pipe, which */
10495             /* would otherwise lose us the PGN.                       */
10496             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10497             /* output during GameEnds should never be fatal anymore   */
10498             if (gameMode == MachinePlaysWhite ||
10499                 gameMode == MachinePlaysBlack ||
10500                 gameMode == TwoMachinesPlay ||
10501                 gameMode == IcsPlayingWhite ||
10502                 gameMode == IcsPlayingBlack ||
10503                 gameMode == BeginningOfGame) {
10504                 char buf[MSG_SIZ];
10505                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10506                         resultDetails);
10507                 if (first.pr != NoProc) {
10508                     SendToProgram(buf, &first);
10509                 }
10510                 if (second.pr != NoProc &&
10511                     gameMode == TwoMachinesPlay) {
10512                     SendToProgram(buf, &second);
10513                 }
10514             }
10515         }
10516
10517         if (appData.icsActive) {
10518             if (appData.quietPlay &&
10519                 (gameMode == IcsPlayingWhite ||
10520                  gameMode == IcsPlayingBlack)) {
10521                 SendToICS(ics_prefix);
10522                 SendToICS("set shout 1\n");
10523             }
10524             nextGameMode = IcsIdle;
10525             ics_user_moved = FALSE;
10526             /* clean up premove.  It's ugly when the game has ended and the
10527              * premove highlights are still on the board.
10528              */
10529             if (gotPremove) {
10530               gotPremove = FALSE;
10531               ClearPremoveHighlights();
10532               DrawPosition(FALSE, boards[currentMove]);
10533             }
10534             if (whosays == GE_ICS) {
10535                 switch (result) {
10536                 case WhiteWins:
10537                     if (gameMode == IcsPlayingWhite)
10538                         PlayIcsWinSound();
10539                     else if(gameMode == IcsPlayingBlack)
10540                         PlayIcsLossSound();
10541                     break;
10542                 case BlackWins:
10543                     if (gameMode == IcsPlayingBlack)
10544                         PlayIcsWinSound();
10545                     else if(gameMode == IcsPlayingWhite)
10546                         PlayIcsLossSound();
10547                     break;
10548                 case GameIsDrawn:
10549                     PlayIcsDrawSound();
10550                     break;
10551                 default:
10552                     PlayIcsUnfinishedSound();
10553                 }
10554             }
10555         } else if (gameMode == EditGame ||
10556                    gameMode == PlayFromGameFile ||
10557                    gameMode == AnalyzeMode ||
10558                    gameMode == AnalyzeFile) {
10559             nextGameMode = gameMode;
10560         } else {
10561             nextGameMode = EndOfGame;
10562         }
10563         pausing = FALSE;
10564         ModeHighlight();
10565     } else {
10566         nextGameMode = gameMode;
10567     }
10568
10569     if (appData.noChessProgram) {
10570         gameMode = nextGameMode;
10571         ModeHighlight();
10572         endingGame = 0; /* [HGM] crash */
10573         return;
10574     }
10575
10576     if (first.reuse) {
10577         /* Put first chess program into idle state */
10578         if (first.pr != NoProc &&
10579             (gameMode == MachinePlaysWhite ||
10580              gameMode == MachinePlaysBlack ||
10581              gameMode == TwoMachinesPlay ||
10582              gameMode == IcsPlayingWhite ||
10583              gameMode == IcsPlayingBlack ||
10584              gameMode == BeginningOfGame)) {
10585             SendToProgram("force\n", &first);
10586             if (first.usePing) {
10587               char buf[MSG_SIZ];
10588               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10589               SendToProgram(buf, &first);
10590             }
10591         }
10592     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10593         /* Kill off first chess program */
10594         if (first.isr != NULL)
10595           RemoveInputSource(first.isr);
10596         first.isr = NULL;
10597
10598         if (first.pr != NoProc) {
10599             ExitAnalyzeMode();
10600             DoSleep( appData.delayBeforeQuit );
10601             SendToProgram("quit\n", &first);
10602             DoSleep( appData.delayAfterQuit );
10603             DestroyChildProcess(first.pr, first.useSigterm);
10604         }
10605         first.pr = NoProc;
10606     }
10607     if (second.reuse) {
10608         /* Put second chess program into idle state */
10609         if (second.pr != NoProc &&
10610             gameMode == TwoMachinesPlay) {
10611             SendToProgram("force\n", &second);
10612             if (second.usePing) {
10613               char buf[MSG_SIZ];
10614               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10615               SendToProgram(buf, &second);
10616             }
10617         }
10618     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10619         /* Kill off second chess program */
10620         if (second.isr != NULL)
10621           RemoveInputSource(second.isr);
10622         second.isr = NULL;
10623
10624         if (second.pr != NoProc) {
10625             DoSleep( appData.delayBeforeQuit );
10626             SendToProgram("quit\n", &second);
10627             DoSleep( appData.delayAfterQuit );
10628             DestroyChildProcess(second.pr, second.useSigterm);
10629         }
10630         second.pr = NoProc;
10631     }
10632
10633     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10634         char resChar = '=';
10635         switch (result) {
10636         case WhiteWins:
10637           resChar = '+';
10638           if (first.twoMachinesColor[0] == 'w') {
10639             first.matchWins++;
10640           } else {
10641             second.matchWins++;
10642           }
10643           break;
10644         case BlackWins:
10645           resChar = '-';
10646           if (first.twoMachinesColor[0] == 'b') {
10647             first.matchWins++;
10648           } else {
10649             second.matchWins++;
10650           }
10651           break;
10652         case GameUnfinished:
10653           resChar = ' ';
10654         default:
10655           break;
10656         }
10657
10658         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10659         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10660             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10661             ReserveGame(nextGame, resChar); // sets nextGame
10662             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10663             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10664         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10665
10666         if (nextGame <= appData.matchGames && !abortMatch) {
10667             gameMode = nextGameMode;
10668             matchGame = nextGame; // this will be overruled in tourney mode!
10669             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10670             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10671             endingGame = 0; /* [HGM] crash */
10672             return;
10673         } else {
10674             gameMode = nextGameMode;
10675             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10676                      first.tidy, second.tidy,
10677                      first.matchWins, second.matchWins,
10678                      appData.matchGames - (first.matchWins + second.matchWins));
10679             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10680             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10681             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10682             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10683                 first.twoMachinesColor = "black\n";
10684                 second.twoMachinesColor = "white\n";
10685             } else {
10686                 first.twoMachinesColor = "white\n";
10687                 second.twoMachinesColor = "black\n";
10688             }
10689         }
10690     }
10691     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10692         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10693       ExitAnalyzeMode();
10694     gameMode = nextGameMode;
10695     ModeHighlight();
10696     endingGame = 0;  /* [HGM] crash */
10697     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10698         if(matchMode == TRUE) { // match through command line: exit with or without popup
10699             if(ranking) {
10700                 ToNrEvent(forwardMostMove);
10701                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10702                 else ExitEvent(0);
10703             } else DisplayFatalError(buf, 0, 0);
10704         } else { // match through menu; just stop, with or without popup
10705             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10706             ModeHighlight();
10707             if(ranking){
10708                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10709             } else DisplayNote(buf);
10710       }
10711       if(ranking) free(ranking);
10712     }
10713 }
10714
10715 /* Assumes program was just initialized (initString sent).
10716    Leaves program in force mode. */
10717 void
10718 FeedMovesToProgram (ChessProgramState *cps, int upto)
10719 {
10720     int i;
10721
10722     if (appData.debugMode)
10723       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10724               startedFromSetupPosition ? "position and " : "",
10725               backwardMostMove, upto, cps->which);
10726     if(currentlyInitializedVariant != gameInfo.variant) {
10727       char buf[MSG_SIZ];
10728         // [HGM] variantswitch: make engine aware of new variant
10729         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10730                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10731         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10732         SendToProgram(buf, cps);
10733         currentlyInitializedVariant = gameInfo.variant;
10734     }
10735     SendToProgram("force\n", cps);
10736     if (startedFromSetupPosition) {
10737         SendBoard(cps, backwardMostMove);
10738     if (appData.debugMode) {
10739         fprintf(debugFP, "feedMoves\n");
10740     }
10741     }
10742     for (i = backwardMostMove; i < upto; i++) {
10743         SendMoveToProgram(i, cps);
10744     }
10745 }
10746
10747
10748 int
10749 ResurrectChessProgram ()
10750 {
10751      /* The chess program may have exited.
10752         If so, restart it and feed it all the moves made so far. */
10753     static int doInit = 0;
10754
10755     if (appData.noChessProgram) return 1;
10756
10757     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10758         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10759         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10760         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10761     } else {
10762         if (first.pr != NoProc) return 1;
10763         StartChessProgram(&first);
10764     }
10765     InitChessProgram(&first, FALSE);
10766     FeedMovesToProgram(&first, currentMove);
10767
10768     if (!first.sendTime) {
10769         /* can't tell gnuchess what its clock should read,
10770            so we bow to its notion. */
10771         ResetClocks();
10772         timeRemaining[0][currentMove] = whiteTimeRemaining;
10773         timeRemaining[1][currentMove] = blackTimeRemaining;
10774     }
10775
10776     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10777                 appData.icsEngineAnalyze) && first.analysisSupport) {
10778       SendToProgram("analyze\n", &first);
10779       first.analyzing = TRUE;
10780     }
10781     return 1;
10782 }
10783
10784 /*
10785  * Button procedures
10786  */
10787 void
10788 Reset (int redraw, int init)
10789 {
10790     int i;
10791
10792     if (appData.debugMode) {
10793         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10794                 redraw, init, gameMode);
10795     }
10796     CleanupTail(); // [HGM] vari: delete any stored variations
10797     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10798     pausing = pauseExamInvalid = FALSE;
10799     startedFromSetupPosition = blackPlaysFirst = FALSE;
10800     firstMove = TRUE;
10801     whiteFlag = blackFlag = FALSE;
10802     userOfferedDraw = FALSE;
10803     hintRequested = bookRequested = FALSE;
10804     first.maybeThinking = FALSE;
10805     second.maybeThinking = FALSE;
10806     first.bookSuspend = FALSE; // [HGM] book
10807     second.bookSuspend = FALSE;
10808     thinkOutput[0] = NULLCHAR;
10809     lastHint[0] = NULLCHAR;
10810     ClearGameInfo(&gameInfo);
10811     gameInfo.variant = StringToVariant(appData.variant);
10812     ics_user_moved = ics_clock_paused = FALSE;
10813     ics_getting_history = H_FALSE;
10814     ics_gamenum = -1;
10815     white_holding[0] = black_holding[0] = NULLCHAR;
10816     ClearProgramStats();
10817     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10818
10819     ResetFrontEnd();
10820     ClearHighlights();
10821     flipView = appData.flipView;
10822     ClearPremoveHighlights();
10823     gotPremove = FALSE;
10824     alarmSounded = FALSE;
10825
10826     GameEnds(EndOfFile, NULL, GE_PLAYER);
10827     if(appData.serverMovesName != NULL) {
10828         /* [HGM] prepare to make moves file for broadcasting */
10829         clock_t t = clock();
10830         if(serverMoves != NULL) fclose(serverMoves);
10831         serverMoves = fopen(appData.serverMovesName, "r");
10832         if(serverMoves != NULL) {
10833             fclose(serverMoves);
10834             /* delay 15 sec before overwriting, so all clients can see end */
10835             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10836         }
10837         serverMoves = fopen(appData.serverMovesName, "w");
10838     }
10839
10840     ExitAnalyzeMode();
10841     gameMode = BeginningOfGame;
10842     ModeHighlight();
10843     if(appData.icsActive) gameInfo.variant = VariantNormal;
10844     currentMove = forwardMostMove = backwardMostMove = 0;
10845     MarkTargetSquares(1);
10846     InitPosition(redraw);
10847     for (i = 0; i < MAX_MOVES; i++) {
10848         if (commentList[i] != NULL) {
10849             free(commentList[i]);
10850             commentList[i] = NULL;
10851         }
10852     }
10853     ResetClocks();
10854     timeRemaining[0][0] = whiteTimeRemaining;
10855     timeRemaining[1][0] = blackTimeRemaining;
10856
10857     if (first.pr == NoProc) {
10858         StartChessProgram(&first);
10859     }
10860     if (init) {
10861             InitChessProgram(&first, startedFromSetupPosition);
10862     }
10863     DisplayTitle("");
10864     DisplayMessage("", "");
10865     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10866     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10867     ClearMap();        // [HGM] exclude: invalidate map
10868 }
10869
10870 void
10871 AutoPlayGameLoop ()
10872 {
10873     for (;;) {
10874         if (!AutoPlayOneMove())
10875           return;
10876         if (matchMode || appData.timeDelay == 0)
10877           continue;
10878         if (appData.timeDelay < 0)
10879           return;
10880         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10881         break;
10882     }
10883 }
10884
10885
10886 int
10887 AutoPlayOneMove ()
10888 {
10889     int fromX, fromY, toX, toY;
10890
10891     if (appData.debugMode) {
10892       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10893     }
10894
10895     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10896       return FALSE;
10897
10898     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10899       pvInfoList[currentMove].depth = programStats.depth;
10900       pvInfoList[currentMove].score = programStats.score;
10901       pvInfoList[currentMove].time  = 0;
10902       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10903     }
10904
10905     if (currentMove >= forwardMostMove) {
10906       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10907 //      gameMode = EndOfGame;
10908 //      ModeHighlight();
10909
10910       /* [AS] Clear current move marker at the end of a game */
10911       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10912
10913       return FALSE;
10914     }
10915
10916     toX = moveList[currentMove][2] - AAA;
10917     toY = moveList[currentMove][3] - ONE;
10918
10919     if (moveList[currentMove][1] == '@') {
10920         if (appData.highlightLastMove) {
10921             SetHighlights(-1, -1, toX, toY);
10922         }
10923     } else {
10924         fromX = moveList[currentMove][0] - AAA;
10925         fromY = moveList[currentMove][1] - ONE;
10926
10927         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10928
10929         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10930
10931         if (appData.highlightLastMove) {
10932             SetHighlights(fromX, fromY, toX, toY);
10933         }
10934     }
10935     DisplayMove(currentMove);
10936     SendMoveToProgram(currentMove++, &first);
10937     DisplayBothClocks();
10938     DrawPosition(FALSE, boards[currentMove]);
10939     // [HGM] PV info: always display, routine tests if empty
10940     DisplayComment(currentMove - 1, commentList[currentMove]);
10941     return TRUE;
10942 }
10943
10944
10945 int
10946 LoadGameOneMove (ChessMove readAhead)
10947 {
10948     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10949     char promoChar = NULLCHAR;
10950     ChessMove moveType;
10951     char move[MSG_SIZ];
10952     char *p, *q;
10953
10954     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10955         gameMode != AnalyzeMode && gameMode != Training) {
10956         gameFileFP = NULL;
10957         return FALSE;
10958     }
10959
10960     yyboardindex = forwardMostMove;
10961     if (readAhead != EndOfFile) {
10962       moveType = readAhead;
10963     } else {
10964       if (gameFileFP == NULL)
10965           return FALSE;
10966       moveType = (ChessMove) Myylex();
10967     }
10968
10969     done = FALSE;
10970     switch (moveType) {
10971       case Comment:
10972         if (appData.debugMode)
10973           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10974         p = yy_text;
10975
10976         /* append the comment but don't display it */
10977         AppendComment(currentMove, p, FALSE);
10978         return TRUE;
10979
10980       case WhiteCapturesEnPassant:
10981       case BlackCapturesEnPassant:
10982       case WhitePromotion:
10983       case BlackPromotion:
10984       case WhiteNonPromotion:
10985       case BlackNonPromotion:
10986       case NormalMove:
10987       case WhiteKingSideCastle:
10988       case WhiteQueenSideCastle:
10989       case BlackKingSideCastle:
10990       case BlackQueenSideCastle:
10991       case WhiteKingSideCastleWild:
10992       case WhiteQueenSideCastleWild:
10993       case BlackKingSideCastleWild:
10994       case BlackQueenSideCastleWild:
10995       /* PUSH Fabien */
10996       case WhiteHSideCastleFR:
10997       case WhiteASideCastleFR:
10998       case BlackHSideCastleFR:
10999       case BlackASideCastleFR:
11000       /* POP Fabien */
11001         if (appData.debugMode)
11002           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11003         fromX = currentMoveString[0] - AAA;
11004         fromY = currentMoveString[1] - ONE;
11005         toX = currentMoveString[2] - AAA;
11006         toY = currentMoveString[3] - ONE;
11007         promoChar = currentMoveString[4];
11008         break;
11009
11010       case WhiteDrop:
11011       case BlackDrop:
11012         if (appData.debugMode)
11013           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11014         fromX = moveType == WhiteDrop ?
11015           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11016         (int) CharToPiece(ToLower(currentMoveString[0]));
11017         fromY = DROP_RANK;
11018         toX = currentMoveString[2] - AAA;
11019         toY = currentMoveString[3] - ONE;
11020         break;
11021
11022       case WhiteWins:
11023       case BlackWins:
11024       case GameIsDrawn:
11025       case GameUnfinished:
11026         if (appData.debugMode)
11027           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11028         p = strchr(yy_text, '{');
11029         if (p == NULL) p = strchr(yy_text, '(');
11030         if (p == NULL) {
11031             p = yy_text;
11032             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11033         } else {
11034             q = strchr(p, *p == '{' ? '}' : ')');
11035             if (q != NULL) *q = NULLCHAR;
11036             p++;
11037         }
11038         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11039         GameEnds(moveType, p, GE_FILE);
11040         done = TRUE;
11041         if (cmailMsgLoaded) {
11042             ClearHighlights();
11043             flipView = WhiteOnMove(currentMove);
11044             if (moveType == GameUnfinished) flipView = !flipView;
11045             if (appData.debugMode)
11046               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11047         }
11048         break;
11049
11050       case EndOfFile:
11051         if (appData.debugMode)
11052           fprintf(debugFP, "Parser hit end of file\n");
11053         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11054           case MT_NONE:
11055           case MT_CHECK:
11056             break;
11057           case MT_CHECKMATE:
11058           case MT_STAINMATE:
11059             if (WhiteOnMove(currentMove)) {
11060                 GameEnds(BlackWins, "Black mates", GE_FILE);
11061             } else {
11062                 GameEnds(WhiteWins, "White mates", GE_FILE);
11063             }
11064             break;
11065           case MT_STALEMATE:
11066             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11067             break;
11068         }
11069         done = TRUE;
11070         break;
11071
11072       case MoveNumberOne:
11073         if (lastLoadGameStart == GNUChessGame) {
11074             /* GNUChessGames have numbers, but they aren't move numbers */
11075             if (appData.debugMode)
11076               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11077                       yy_text, (int) moveType);
11078             return LoadGameOneMove(EndOfFile); /* tail recursion */
11079         }
11080         /* else fall thru */
11081
11082       case XBoardGame:
11083       case GNUChessGame:
11084       case PGNTag:
11085         /* Reached start of next game in file */
11086         if (appData.debugMode)
11087           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11088         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11089           case MT_NONE:
11090           case MT_CHECK:
11091             break;
11092           case MT_CHECKMATE:
11093           case MT_STAINMATE:
11094             if (WhiteOnMove(currentMove)) {
11095                 GameEnds(BlackWins, "Black mates", GE_FILE);
11096             } else {
11097                 GameEnds(WhiteWins, "White mates", GE_FILE);
11098             }
11099             break;
11100           case MT_STALEMATE:
11101             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11102             break;
11103         }
11104         done = TRUE;
11105         break;
11106
11107       case PositionDiagram:     /* should not happen; ignore */
11108       case ElapsedTime:         /* ignore */
11109       case NAG:                 /* ignore */
11110         if (appData.debugMode)
11111           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11112                   yy_text, (int) moveType);
11113         return LoadGameOneMove(EndOfFile); /* tail recursion */
11114
11115       case IllegalMove:
11116         if (appData.testLegality) {
11117             if (appData.debugMode)
11118               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11119             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11120                     (forwardMostMove / 2) + 1,
11121                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11122             DisplayError(move, 0);
11123             done = TRUE;
11124         } else {
11125             if (appData.debugMode)
11126               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11127                       yy_text, currentMoveString);
11128             fromX = currentMoveString[0] - AAA;
11129             fromY = currentMoveString[1] - ONE;
11130             toX = currentMoveString[2] - AAA;
11131             toY = currentMoveString[3] - ONE;
11132             promoChar = currentMoveString[4];
11133         }
11134         break;
11135
11136       case AmbiguousMove:
11137         if (appData.debugMode)
11138           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11139         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11140                 (forwardMostMove / 2) + 1,
11141                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11142         DisplayError(move, 0);
11143         done = TRUE;
11144         break;
11145
11146       default:
11147       case ImpossibleMove:
11148         if (appData.debugMode)
11149           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11150         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11151                 (forwardMostMove / 2) + 1,
11152                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11153         DisplayError(move, 0);
11154         done = TRUE;
11155         break;
11156     }
11157
11158     if (done) {
11159         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11160             DrawPosition(FALSE, boards[currentMove]);
11161             DisplayBothClocks();
11162             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11163               DisplayComment(currentMove - 1, commentList[currentMove]);
11164         }
11165         (void) StopLoadGameTimer();
11166         gameFileFP = NULL;
11167         cmailOldMove = forwardMostMove;
11168         return FALSE;
11169     } else {
11170         /* currentMoveString is set as a side-effect of yylex */
11171
11172         thinkOutput[0] = NULLCHAR;
11173         MakeMove(fromX, fromY, toX, toY, promoChar);
11174         currentMove = forwardMostMove;
11175         return TRUE;
11176     }
11177 }
11178
11179 /* Load the nth game from the given file */
11180 int
11181 LoadGameFromFile (char *filename, int n, char *title, int useList)
11182 {
11183     FILE *f;
11184     char buf[MSG_SIZ];
11185
11186     if (strcmp(filename, "-") == 0) {
11187         f = stdin;
11188         title = "stdin";
11189     } else {
11190         f = fopen(filename, "rb");
11191         if (f == NULL) {
11192           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11193             DisplayError(buf, errno);
11194             return FALSE;
11195         }
11196     }
11197     if (fseek(f, 0, 0) == -1) {
11198         /* f is not seekable; probably a pipe */
11199         useList = FALSE;
11200     }
11201     if (useList && n == 0) {
11202         int error = GameListBuild(f);
11203         if (error) {
11204             DisplayError(_("Cannot build game list"), error);
11205         } else if (!ListEmpty(&gameList) &&
11206                    ((ListGame *) gameList.tailPred)->number > 1) {
11207             GameListPopUp(f, title);
11208             return TRUE;
11209         }
11210         GameListDestroy();
11211         n = 1;
11212     }
11213     if (n == 0) n = 1;
11214     return LoadGame(f, n, title, FALSE);
11215 }
11216
11217
11218 void
11219 MakeRegisteredMove ()
11220 {
11221     int fromX, fromY, toX, toY;
11222     char promoChar;
11223     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11224         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11225           case CMAIL_MOVE:
11226           case CMAIL_DRAW:
11227             if (appData.debugMode)
11228               fprintf(debugFP, "Restoring %s for game %d\n",
11229                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11230
11231             thinkOutput[0] = NULLCHAR;
11232             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11233             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11234             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11235             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11236             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11237             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11238             MakeMove(fromX, fromY, toX, toY, promoChar);
11239             ShowMove(fromX, fromY, toX, toY);
11240
11241             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11242               case MT_NONE:
11243               case MT_CHECK:
11244                 break;
11245
11246               case MT_CHECKMATE:
11247               case MT_STAINMATE:
11248                 if (WhiteOnMove(currentMove)) {
11249                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11250                 } else {
11251                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11252                 }
11253                 break;
11254
11255               case MT_STALEMATE:
11256                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11257                 break;
11258             }
11259
11260             break;
11261
11262           case CMAIL_RESIGN:
11263             if (WhiteOnMove(currentMove)) {
11264                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11265             } else {
11266                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11267             }
11268             break;
11269
11270           case CMAIL_ACCEPT:
11271             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11272             break;
11273
11274           default:
11275             break;
11276         }
11277     }
11278
11279     return;
11280 }
11281
11282 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11283 int
11284 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11285 {
11286     int retVal;
11287
11288     if (gameNumber > nCmailGames) {
11289         DisplayError(_("No more games in this message"), 0);
11290         return FALSE;
11291     }
11292     if (f == lastLoadGameFP) {
11293         int offset = gameNumber - lastLoadGameNumber;
11294         if (offset == 0) {
11295             cmailMsg[0] = NULLCHAR;
11296             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11297                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11298                 nCmailMovesRegistered--;
11299             }
11300             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11301             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11302                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11303             }
11304         } else {
11305             if (! RegisterMove()) return FALSE;
11306         }
11307     }
11308
11309     retVal = LoadGame(f, gameNumber, title, useList);
11310
11311     /* Make move registered during previous look at this game, if any */
11312     MakeRegisteredMove();
11313
11314     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11315         commentList[currentMove]
11316           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11317         DisplayComment(currentMove - 1, commentList[currentMove]);
11318     }
11319
11320     return retVal;
11321 }
11322
11323 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11324 int
11325 ReloadGame (int offset)
11326 {
11327     int gameNumber = lastLoadGameNumber + offset;
11328     if (lastLoadGameFP == NULL) {
11329         DisplayError(_("No game has been loaded yet"), 0);
11330         return FALSE;
11331     }
11332     if (gameNumber <= 0) {
11333         DisplayError(_("Can't back up any further"), 0);
11334         return FALSE;
11335     }
11336     if (cmailMsgLoaded) {
11337         return CmailLoadGame(lastLoadGameFP, gameNumber,
11338                              lastLoadGameTitle, lastLoadGameUseList);
11339     } else {
11340         return LoadGame(lastLoadGameFP, gameNumber,
11341                         lastLoadGameTitle, lastLoadGameUseList);
11342     }
11343 }
11344
11345 int keys[EmptySquare+1];
11346
11347 int
11348 PositionMatches (Board b1, Board b2)
11349 {
11350     int r, f, sum=0;
11351     switch(appData.searchMode) {
11352         case 1: return CompareWithRights(b1, b2);
11353         case 2:
11354             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11355                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11356             }
11357             return TRUE;
11358         case 3:
11359             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11360               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11361                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11362             }
11363             return sum==0;
11364         case 4:
11365             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11366                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11367             }
11368             return sum==0;
11369     }
11370     return TRUE;
11371 }
11372
11373 #define Q_PROMO  4
11374 #define Q_EP     3
11375 #define Q_BCASTL 2
11376 #define Q_WCASTL 1
11377
11378 int pieceList[256], quickBoard[256];
11379 ChessSquare pieceType[256] = { EmptySquare };
11380 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11381 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11382 int soughtTotal, turn;
11383 Boolean epOK, flipSearch;
11384
11385 typedef struct {
11386     unsigned char piece, to;
11387 } Move;
11388
11389 #define DSIZE (250000)
11390
11391 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11392 Move *moveDatabase = initialSpace;
11393 unsigned int movePtr, dataSize = DSIZE;
11394
11395 int
11396 MakePieceList (Board board, int *counts)
11397 {
11398     int r, f, n=Q_PROMO, total=0;
11399     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11400     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11401         int sq = f + (r<<4);
11402         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11403             quickBoard[sq] = ++n;
11404             pieceList[n] = sq;
11405             pieceType[n] = board[r][f];
11406             counts[board[r][f]]++;
11407             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11408             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11409             total++;
11410         }
11411     }
11412     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11413     return total;
11414 }
11415
11416 void
11417 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11418 {
11419     int sq = fromX + (fromY<<4);
11420     int piece = quickBoard[sq];
11421     quickBoard[sq] = 0;
11422     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11423     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11424         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11425         moveDatabase[movePtr++].piece = Q_WCASTL;
11426         quickBoard[sq] = piece;
11427         piece = quickBoard[from]; quickBoard[from] = 0;
11428         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11429     } else
11430     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11431         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11432         moveDatabase[movePtr++].piece = Q_BCASTL;
11433         quickBoard[sq] = piece;
11434         piece = quickBoard[from]; quickBoard[from] = 0;
11435         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11436     } else
11437     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11438         quickBoard[(fromY<<4)+toX] = 0;
11439         moveDatabase[movePtr].piece = Q_EP;
11440         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11441         moveDatabase[movePtr].to = sq;
11442     } else
11443     if(promoPiece != pieceType[piece]) {
11444         moveDatabase[movePtr++].piece = Q_PROMO;
11445         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11446     }
11447     moveDatabase[movePtr].piece = piece;
11448     quickBoard[sq] = piece;
11449     movePtr++;
11450 }
11451
11452 int
11453 PackGame (Board board)
11454 {
11455     Move *newSpace = NULL;
11456     moveDatabase[movePtr].piece = 0; // terminate previous game
11457     if(movePtr > dataSize) {
11458         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11459         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11460         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11461         if(newSpace) {
11462             int i;
11463             Move *p = moveDatabase, *q = newSpace;
11464             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11465             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11466             moveDatabase = newSpace;
11467         } else { // calloc failed, we must be out of memory. Too bad...
11468             dataSize = 0; // prevent calloc events for all subsequent games
11469             return 0;     // and signal this one isn't cached
11470         }
11471     }
11472     movePtr++;
11473     MakePieceList(board, counts);
11474     return movePtr;
11475 }
11476
11477 int
11478 QuickCompare (Board board, int *minCounts, int *maxCounts)
11479 {   // compare according to search mode
11480     int r, f;
11481     switch(appData.searchMode)
11482     {
11483       case 1: // exact position match
11484         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11485         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11486             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11487         }
11488         break;
11489       case 2: // can have extra material on empty squares
11490         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11491             if(board[r][f] == EmptySquare) continue;
11492             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11493         }
11494         break;
11495       case 3: // material with exact Pawn structure
11496         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11497             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11498             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11499         } // fall through to material comparison
11500       case 4: // exact material
11501         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11502         break;
11503       case 6: // material range with given imbalance
11504         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11505         // fall through to range comparison
11506       case 5: // material range
11507         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11508     }
11509     return TRUE;
11510 }
11511
11512 int
11513 QuickScan (Board board, Move *move)
11514 {   // reconstruct game,and compare all positions in it
11515     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11516     do {
11517         int piece = move->piece;
11518         int to = move->to, from = pieceList[piece];
11519         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11520           if(!piece) return -1;
11521           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11522             piece = (++move)->piece;
11523             from = pieceList[piece];
11524             counts[pieceType[piece]]--;
11525             pieceType[piece] = (ChessSquare) move->to;
11526             counts[move->to]++;
11527           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11528             counts[pieceType[quickBoard[to]]]--;
11529             quickBoard[to] = 0; total--;
11530             move++;
11531             continue;
11532           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11533             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11534             from  = pieceList[piece]; // so this must be King
11535             quickBoard[from] = 0;
11536             quickBoard[to] = piece;
11537             pieceList[piece] = to;
11538             move++;
11539             continue;
11540           }
11541         }
11542         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11543         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11544         quickBoard[from] = 0;
11545         quickBoard[to] = piece;
11546         pieceList[piece] = to;
11547         cnt++; turn ^= 3;
11548         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11549            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11550            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11551                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11552           ) {
11553             static int lastCounts[EmptySquare+1];
11554             int i;
11555             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11556             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11557         } else stretch = 0;
11558         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11559         move++;
11560     } while(1);
11561 }
11562
11563 void
11564 InitSearch ()
11565 {
11566     int r, f;
11567     flipSearch = FALSE;
11568     CopyBoard(soughtBoard, boards[currentMove]);
11569     soughtTotal = MakePieceList(soughtBoard, maxSought);
11570     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11571     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11572     CopyBoard(reverseBoard, boards[currentMove]);
11573     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11574         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11575         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11576         reverseBoard[r][f] = piece;
11577     }
11578     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11579     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11580     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11581                  || (boards[currentMove][CASTLING][2] == NoRights || 
11582                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11583                  && (boards[currentMove][CASTLING][5] == NoRights || 
11584                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11585       ) {
11586         flipSearch = TRUE;
11587         CopyBoard(flipBoard, soughtBoard);
11588         CopyBoard(rotateBoard, reverseBoard);
11589         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11590             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11591             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11592         }
11593     }
11594     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11595     if(appData.searchMode >= 5) {
11596         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11597         MakePieceList(soughtBoard, minSought);
11598         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11599     }
11600     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11601         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11602 }
11603
11604 GameInfo dummyInfo;
11605
11606 int
11607 GameContainsPosition (FILE *f, ListGame *lg)
11608 {
11609     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11610     int fromX, fromY, toX, toY;
11611     char promoChar;
11612     static int initDone=FALSE;
11613
11614     // weed out games based on numerical tag comparison
11615     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11616     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11617     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11618     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11619     if(!initDone) {
11620         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11621         initDone = TRUE;
11622     }
11623     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11624     else CopyBoard(boards[scratch], initialPosition); // default start position
11625     if(lg->moves) {
11626         turn = btm + 1;
11627         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11628         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11629     }
11630     if(btm) plyNr++;
11631     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11632     fseek(f, lg->offset, 0);
11633     yynewfile(f);
11634     while(1) {
11635         yyboardindex = scratch;
11636         quickFlag = plyNr+1;
11637         next = Myylex();
11638         quickFlag = 0;
11639         switch(next) {
11640             case PGNTag:
11641                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11642             default:
11643                 continue;
11644
11645             case XBoardGame:
11646             case GNUChessGame:
11647                 if(plyNr) return -1; // after we have seen moves, this is for new game
11648               continue;
11649
11650             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11651             case ImpossibleMove:
11652             case WhiteWins: // game ends here with these four
11653             case BlackWins:
11654             case GameIsDrawn:
11655             case GameUnfinished:
11656                 return -1;
11657
11658             case IllegalMove:
11659                 if(appData.testLegality) return -1;
11660             case WhiteCapturesEnPassant:
11661             case BlackCapturesEnPassant:
11662             case WhitePromotion:
11663             case BlackPromotion:
11664             case WhiteNonPromotion:
11665             case BlackNonPromotion:
11666             case NormalMove:
11667             case WhiteKingSideCastle:
11668             case WhiteQueenSideCastle:
11669             case BlackKingSideCastle:
11670             case BlackQueenSideCastle:
11671             case WhiteKingSideCastleWild:
11672             case WhiteQueenSideCastleWild:
11673             case BlackKingSideCastleWild:
11674             case BlackQueenSideCastleWild:
11675             case WhiteHSideCastleFR:
11676             case WhiteASideCastleFR:
11677             case BlackHSideCastleFR:
11678             case BlackASideCastleFR:
11679                 fromX = currentMoveString[0] - AAA;
11680                 fromY = currentMoveString[1] - ONE;
11681                 toX = currentMoveString[2] - AAA;
11682                 toY = currentMoveString[3] - ONE;
11683                 promoChar = currentMoveString[4];
11684                 break;
11685             case WhiteDrop:
11686             case BlackDrop:
11687                 fromX = next == WhiteDrop ?
11688                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11689                   (int) CharToPiece(ToLower(currentMoveString[0]));
11690                 fromY = DROP_RANK;
11691                 toX = currentMoveString[2] - AAA;
11692                 toY = currentMoveString[3] - ONE;
11693                 promoChar = 0;
11694                 break;
11695         }
11696         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11697         plyNr++;
11698         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11699         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11700         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11701         if(appData.findMirror) {
11702             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11703             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11704         }
11705     }
11706 }
11707
11708 /* Load the nth game from open file f */
11709 int
11710 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11711 {
11712     ChessMove cm;
11713     char buf[MSG_SIZ];
11714     int gn = gameNumber;
11715     ListGame *lg = NULL;
11716     int numPGNTags = 0;
11717     int err, pos = -1;
11718     GameMode oldGameMode;
11719     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11720
11721     if (appData.debugMode)
11722         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11723
11724     if (gameMode == Training )
11725         SetTrainingModeOff();
11726
11727     oldGameMode = gameMode;
11728     if (gameMode != BeginningOfGame) {
11729       Reset(FALSE, TRUE);
11730     }
11731
11732     gameFileFP = f;
11733     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11734         fclose(lastLoadGameFP);
11735     }
11736
11737     if (useList) {
11738         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11739
11740         if (lg) {
11741             fseek(f, lg->offset, 0);
11742             GameListHighlight(gameNumber);
11743             pos = lg->position;
11744             gn = 1;
11745         }
11746         else {
11747             DisplayError(_("Game number out of range"), 0);
11748             return FALSE;
11749         }
11750     } else {
11751         GameListDestroy();
11752         if (fseek(f, 0, 0) == -1) {
11753             if (f == lastLoadGameFP ?
11754                 gameNumber == lastLoadGameNumber + 1 :
11755                 gameNumber == 1) {
11756                 gn = 1;
11757             } else {
11758                 DisplayError(_("Can't seek on game file"), 0);
11759                 return FALSE;
11760             }
11761         }
11762     }
11763     lastLoadGameFP = f;
11764     lastLoadGameNumber = gameNumber;
11765     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11766     lastLoadGameUseList = useList;
11767
11768     yynewfile(f);
11769
11770     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11771       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11772                 lg->gameInfo.black);
11773             DisplayTitle(buf);
11774     } else if (*title != NULLCHAR) {
11775         if (gameNumber > 1) {
11776           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11777             DisplayTitle(buf);
11778         } else {
11779             DisplayTitle(title);
11780         }
11781     }
11782
11783     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11784         gameMode = PlayFromGameFile;
11785         ModeHighlight();
11786     }
11787
11788     currentMove = forwardMostMove = backwardMostMove = 0;
11789     CopyBoard(boards[0], initialPosition);
11790     StopClocks();
11791
11792     /*
11793      * Skip the first gn-1 games in the file.
11794      * Also skip over anything that precedes an identifiable
11795      * start of game marker, to avoid being confused by
11796      * garbage at the start of the file.  Currently
11797      * recognized start of game markers are the move number "1",
11798      * the pattern "gnuchess .* game", the pattern
11799      * "^[#;%] [^ ]* game file", and a PGN tag block.
11800      * A game that starts with one of the latter two patterns
11801      * will also have a move number 1, possibly
11802      * following a position diagram.
11803      * 5-4-02: Let's try being more lenient and allowing a game to
11804      * start with an unnumbered move.  Does that break anything?
11805      */
11806     cm = lastLoadGameStart = EndOfFile;
11807     while (gn > 0) {
11808         yyboardindex = forwardMostMove;
11809         cm = (ChessMove) Myylex();
11810         switch (cm) {
11811           case EndOfFile:
11812             if (cmailMsgLoaded) {
11813                 nCmailGames = CMAIL_MAX_GAMES - gn;
11814             } else {
11815                 Reset(TRUE, TRUE);
11816                 DisplayError(_("Game not found in file"), 0);
11817             }
11818             return FALSE;
11819
11820           case GNUChessGame:
11821           case XBoardGame:
11822             gn--;
11823             lastLoadGameStart = cm;
11824             break;
11825
11826           case MoveNumberOne:
11827             switch (lastLoadGameStart) {
11828               case GNUChessGame:
11829               case XBoardGame:
11830               case PGNTag:
11831                 break;
11832               case MoveNumberOne:
11833               case EndOfFile:
11834                 gn--;           /* count this game */
11835                 lastLoadGameStart = cm;
11836                 break;
11837               default:
11838                 /* impossible */
11839                 break;
11840             }
11841             break;
11842
11843           case PGNTag:
11844             switch (lastLoadGameStart) {
11845               case GNUChessGame:
11846               case PGNTag:
11847               case MoveNumberOne:
11848               case EndOfFile:
11849                 gn--;           /* count this game */
11850                 lastLoadGameStart = cm;
11851                 break;
11852               case XBoardGame:
11853                 lastLoadGameStart = cm; /* game counted already */
11854                 break;
11855               default:
11856                 /* impossible */
11857                 break;
11858             }
11859             if (gn > 0) {
11860                 do {
11861                     yyboardindex = forwardMostMove;
11862                     cm = (ChessMove) Myylex();
11863                 } while (cm == PGNTag || cm == Comment);
11864             }
11865             break;
11866
11867           case WhiteWins:
11868           case BlackWins:
11869           case GameIsDrawn:
11870             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11871                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11872                     != CMAIL_OLD_RESULT) {
11873                     nCmailResults ++ ;
11874                     cmailResult[  CMAIL_MAX_GAMES
11875                                 - gn - 1] = CMAIL_OLD_RESULT;
11876                 }
11877             }
11878             break;
11879
11880           case NormalMove:
11881             /* Only a NormalMove can be at the start of a game
11882              * without a position diagram. */
11883             if (lastLoadGameStart == EndOfFile ) {
11884               gn--;
11885               lastLoadGameStart = MoveNumberOne;
11886             }
11887             break;
11888
11889           default:
11890             break;
11891         }
11892     }
11893
11894     if (appData.debugMode)
11895       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11896
11897     if (cm == XBoardGame) {
11898         /* Skip any header junk before position diagram and/or move 1 */
11899         for (;;) {
11900             yyboardindex = forwardMostMove;
11901             cm = (ChessMove) Myylex();
11902
11903             if (cm == EndOfFile ||
11904                 cm == GNUChessGame || cm == XBoardGame) {
11905                 /* Empty game; pretend end-of-file and handle later */
11906                 cm = EndOfFile;
11907                 break;
11908             }
11909
11910             if (cm == MoveNumberOne || cm == PositionDiagram ||
11911                 cm == PGNTag || cm == Comment)
11912               break;
11913         }
11914     } else if (cm == GNUChessGame) {
11915         if (gameInfo.event != NULL) {
11916             free(gameInfo.event);
11917         }
11918         gameInfo.event = StrSave(yy_text);
11919     }
11920
11921     startedFromSetupPosition = FALSE;
11922     while (cm == PGNTag) {
11923         if (appData.debugMode)
11924           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11925         err = ParsePGNTag(yy_text, &gameInfo);
11926         if (!err) numPGNTags++;
11927
11928         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11929         if(gameInfo.variant != oldVariant) {
11930             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11931             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11932             InitPosition(TRUE);
11933             oldVariant = gameInfo.variant;
11934             if (appData.debugMode)
11935               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11936         }
11937
11938
11939         if (gameInfo.fen != NULL) {
11940           Board initial_position;
11941           startedFromSetupPosition = TRUE;
11942           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11943             Reset(TRUE, TRUE);
11944             DisplayError(_("Bad FEN position in file"), 0);
11945             return FALSE;
11946           }
11947           CopyBoard(boards[0], initial_position);
11948           if (blackPlaysFirst) {
11949             currentMove = forwardMostMove = backwardMostMove = 1;
11950             CopyBoard(boards[1], initial_position);
11951             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11952             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11953             timeRemaining[0][1] = whiteTimeRemaining;
11954             timeRemaining[1][1] = blackTimeRemaining;
11955             if (commentList[0] != NULL) {
11956               commentList[1] = commentList[0];
11957               commentList[0] = NULL;
11958             }
11959           } else {
11960             currentMove = forwardMostMove = backwardMostMove = 0;
11961           }
11962           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11963           {   int i;
11964               initialRulePlies = FENrulePlies;
11965               for( i=0; i< nrCastlingRights; i++ )
11966                   initialRights[i] = initial_position[CASTLING][i];
11967           }
11968           yyboardindex = forwardMostMove;
11969           free(gameInfo.fen);
11970           gameInfo.fen = NULL;
11971         }
11972
11973         yyboardindex = forwardMostMove;
11974         cm = (ChessMove) Myylex();
11975
11976         /* Handle comments interspersed among the tags */
11977         while (cm == Comment) {
11978             char *p;
11979             if (appData.debugMode)
11980               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11981             p = yy_text;
11982             AppendComment(currentMove, p, FALSE);
11983             yyboardindex = forwardMostMove;
11984             cm = (ChessMove) Myylex();
11985         }
11986     }
11987
11988     /* don't rely on existence of Event tag since if game was
11989      * pasted from clipboard the Event tag may not exist
11990      */
11991     if (numPGNTags > 0){
11992         char *tags;
11993         if (gameInfo.variant == VariantNormal) {
11994           VariantClass v = StringToVariant(gameInfo.event);
11995           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11996           if(v < VariantShogi) gameInfo.variant = v;
11997         }
11998         if (!matchMode) {
11999           if( appData.autoDisplayTags ) {
12000             tags = PGNTags(&gameInfo);
12001             TagsPopUp(tags, CmailMsg());
12002             free(tags);
12003           }
12004         }
12005     } else {
12006         /* Make something up, but don't display it now */
12007         SetGameInfo();
12008         TagsPopDown();
12009     }
12010
12011     if (cm == PositionDiagram) {
12012         int i, j;
12013         char *p;
12014         Board initial_position;
12015
12016         if (appData.debugMode)
12017           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12018
12019         if (!startedFromSetupPosition) {
12020             p = yy_text;
12021             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12022               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12023                 switch (*p) {
12024                   case '{':
12025                   case '[':
12026                   case '-':
12027                   case ' ':
12028                   case '\t':
12029                   case '\n':
12030                   case '\r':
12031                     break;
12032                   default:
12033                     initial_position[i][j++] = CharToPiece(*p);
12034                     break;
12035                 }
12036             while (*p == ' ' || *p == '\t' ||
12037                    *p == '\n' || *p == '\r') p++;
12038
12039             if (strncmp(p, "black", strlen("black"))==0)
12040               blackPlaysFirst = TRUE;
12041             else
12042               blackPlaysFirst = FALSE;
12043             startedFromSetupPosition = TRUE;
12044
12045             CopyBoard(boards[0], initial_position);
12046             if (blackPlaysFirst) {
12047                 currentMove = forwardMostMove = backwardMostMove = 1;
12048                 CopyBoard(boards[1], initial_position);
12049                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12050                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12051                 timeRemaining[0][1] = whiteTimeRemaining;
12052                 timeRemaining[1][1] = blackTimeRemaining;
12053                 if (commentList[0] != NULL) {
12054                     commentList[1] = commentList[0];
12055                     commentList[0] = NULL;
12056                 }
12057             } else {
12058                 currentMove = forwardMostMove = backwardMostMove = 0;
12059             }
12060         }
12061         yyboardindex = forwardMostMove;
12062         cm = (ChessMove) Myylex();
12063     }
12064
12065     if (first.pr == NoProc) {
12066         StartChessProgram(&first);
12067     }
12068     InitChessProgram(&first, FALSE);
12069     SendToProgram("force\n", &first);
12070     if (startedFromSetupPosition) {
12071         SendBoard(&first, forwardMostMove);
12072     if (appData.debugMode) {
12073         fprintf(debugFP, "Load Game\n");
12074     }
12075         DisplayBothClocks();
12076     }
12077
12078     /* [HGM] server: flag to write setup moves in broadcast file as one */
12079     loadFlag = appData.suppressLoadMoves;
12080
12081     while (cm == Comment) {
12082         char *p;
12083         if (appData.debugMode)
12084           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12085         p = yy_text;
12086         AppendComment(currentMove, p, FALSE);
12087         yyboardindex = forwardMostMove;
12088         cm = (ChessMove) Myylex();
12089     }
12090
12091     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12092         cm == WhiteWins || cm == BlackWins ||
12093         cm == GameIsDrawn || cm == GameUnfinished) {
12094         DisplayMessage("", _("No moves in game"));
12095         if (cmailMsgLoaded) {
12096             if (appData.debugMode)
12097               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12098             ClearHighlights();
12099             flipView = FALSE;
12100         }
12101         DrawPosition(FALSE, boards[currentMove]);
12102         DisplayBothClocks();
12103         gameMode = EditGame;
12104         ModeHighlight();
12105         gameFileFP = NULL;
12106         cmailOldMove = 0;
12107         return TRUE;
12108     }
12109
12110     // [HGM] PV info: routine tests if comment empty
12111     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12112         DisplayComment(currentMove - 1, commentList[currentMove]);
12113     }
12114     if (!matchMode && appData.timeDelay != 0)
12115       DrawPosition(FALSE, boards[currentMove]);
12116
12117     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12118       programStats.ok_to_send = 1;
12119     }
12120
12121     /* if the first token after the PGN tags is a move
12122      * and not move number 1, retrieve it from the parser
12123      */
12124     if (cm != MoveNumberOne)
12125         LoadGameOneMove(cm);
12126
12127     /* load the remaining moves from the file */
12128     while (LoadGameOneMove(EndOfFile)) {
12129       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12130       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12131     }
12132
12133     /* rewind to the start of the game */
12134     currentMove = backwardMostMove;
12135
12136     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12137
12138     if (oldGameMode == AnalyzeFile ||
12139         oldGameMode == AnalyzeMode) {
12140       AnalyzeFileEvent();
12141     }
12142
12143     if (!matchMode && pos >= 0) {
12144         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12145     } else
12146     if (matchMode || appData.timeDelay == 0) {
12147       ToEndEvent();
12148     } else if (appData.timeDelay > 0) {
12149       AutoPlayGameLoop();
12150     }
12151
12152     if (appData.debugMode)
12153         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12154
12155     loadFlag = 0; /* [HGM] true game starts */
12156     return TRUE;
12157 }
12158
12159 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12160 int
12161 ReloadPosition (int offset)
12162 {
12163     int positionNumber = lastLoadPositionNumber + offset;
12164     if (lastLoadPositionFP == NULL) {
12165         DisplayError(_("No position has been loaded yet"), 0);
12166         return FALSE;
12167     }
12168     if (positionNumber <= 0) {
12169         DisplayError(_("Can't back up any further"), 0);
12170         return FALSE;
12171     }
12172     return LoadPosition(lastLoadPositionFP, positionNumber,
12173                         lastLoadPositionTitle);
12174 }
12175
12176 /* Load the nth position from the given file */
12177 int
12178 LoadPositionFromFile (char *filename, int n, char *title)
12179 {
12180     FILE *f;
12181     char buf[MSG_SIZ];
12182
12183     if (strcmp(filename, "-") == 0) {
12184         return LoadPosition(stdin, n, "stdin");
12185     } else {
12186         f = fopen(filename, "rb");
12187         if (f == NULL) {
12188             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12189             DisplayError(buf, errno);
12190             return FALSE;
12191         } else {
12192             return LoadPosition(f, n, title);
12193         }
12194     }
12195 }
12196
12197 /* Load the nth position from the given open file, and close it */
12198 int
12199 LoadPosition (FILE *f, int positionNumber, char *title)
12200 {
12201     char *p, line[MSG_SIZ];
12202     Board initial_position;
12203     int i, j, fenMode, pn;
12204
12205     if (gameMode == Training )
12206         SetTrainingModeOff();
12207
12208     if (gameMode != BeginningOfGame) {
12209         Reset(FALSE, TRUE);
12210     }
12211     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12212         fclose(lastLoadPositionFP);
12213     }
12214     if (positionNumber == 0) positionNumber = 1;
12215     lastLoadPositionFP = f;
12216     lastLoadPositionNumber = positionNumber;
12217     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12218     if (first.pr == NoProc && !appData.noChessProgram) {
12219       StartChessProgram(&first);
12220       InitChessProgram(&first, FALSE);
12221     }
12222     pn = positionNumber;
12223     if (positionNumber < 0) {
12224         /* Negative position number means to seek to that byte offset */
12225         if (fseek(f, -positionNumber, 0) == -1) {
12226             DisplayError(_("Can't seek on position file"), 0);
12227             return FALSE;
12228         };
12229         pn = 1;
12230     } else {
12231         if (fseek(f, 0, 0) == -1) {
12232             if (f == lastLoadPositionFP ?
12233                 positionNumber == lastLoadPositionNumber + 1 :
12234                 positionNumber == 1) {
12235                 pn = 1;
12236             } else {
12237                 DisplayError(_("Can't seek on position file"), 0);
12238                 return FALSE;
12239             }
12240         }
12241     }
12242     /* See if this file is FEN or old-style xboard */
12243     if (fgets(line, MSG_SIZ, f) == NULL) {
12244         DisplayError(_("Position not found in file"), 0);
12245         return FALSE;
12246     }
12247     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12248     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12249
12250     if (pn >= 2) {
12251         if (fenMode || line[0] == '#') pn--;
12252         while (pn > 0) {
12253             /* skip positions before number pn */
12254             if (fgets(line, MSG_SIZ, f) == NULL) {
12255                 Reset(TRUE, TRUE);
12256                 DisplayError(_("Position not found in file"), 0);
12257                 return FALSE;
12258             }
12259             if (fenMode || line[0] == '#') pn--;
12260         }
12261     }
12262
12263     if (fenMode) {
12264         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12265             DisplayError(_("Bad FEN position in file"), 0);
12266             return FALSE;
12267         }
12268     } else {
12269         (void) fgets(line, MSG_SIZ, f);
12270         (void) fgets(line, MSG_SIZ, f);
12271
12272         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12273             (void) fgets(line, MSG_SIZ, f);
12274             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12275                 if (*p == ' ')
12276                   continue;
12277                 initial_position[i][j++] = CharToPiece(*p);
12278             }
12279         }
12280
12281         blackPlaysFirst = FALSE;
12282         if (!feof(f)) {
12283             (void) fgets(line, MSG_SIZ, f);
12284             if (strncmp(line, "black", strlen("black"))==0)
12285               blackPlaysFirst = TRUE;
12286         }
12287     }
12288     startedFromSetupPosition = TRUE;
12289
12290     CopyBoard(boards[0], initial_position);
12291     if (blackPlaysFirst) {
12292         currentMove = forwardMostMove = backwardMostMove = 1;
12293         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12294         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12295         CopyBoard(boards[1], initial_position);
12296         DisplayMessage("", _("Black to play"));
12297     } else {
12298         currentMove = forwardMostMove = backwardMostMove = 0;
12299         DisplayMessage("", _("White to play"));
12300     }
12301     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12302     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12303         SendToProgram("force\n", &first);
12304         SendBoard(&first, forwardMostMove);
12305     }
12306     if (appData.debugMode) {
12307 int i, j;
12308   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12309   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12310         fprintf(debugFP, "Load Position\n");
12311     }
12312
12313     if (positionNumber > 1) {
12314       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12315         DisplayTitle(line);
12316     } else {
12317         DisplayTitle(title);
12318     }
12319     gameMode = EditGame;
12320     ModeHighlight();
12321     ResetClocks();
12322     timeRemaining[0][1] = whiteTimeRemaining;
12323     timeRemaining[1][1] = blackTimeRemaining;
12324     DrawPosition(FALSE, boards[currentMove]);
12325
12326     return TRUE;
12327 }
12328
12329
12330 void
12331 CopyPlayerNameIntoFileName (char **dest, char *src)
12332 {
12333     while (*src != NULLCHAR && *src != ',') {
12334         if (*src == ' ') {
12335             *(*dest)++ = '_';
12336             src++;
12337         } else {
12338             *(*dest)++ = *src++;
12339         }
12340     }
12341 }
12342
12343 char *
12344 DefaultFileName (char *ext)
12345 {
12346     static char def[MSG_SIZ];
12347     char *p;
12348
12349     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12350         p = def;
12351         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12352         *p++ = '-';
12353         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12354         *p++ = '.';
12355         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12356     } else {
12357         def[0] = NULLCHAR;
12358     }
12359     return def;
12360 }
12361
12362 /* Save the current game to the given file */
12363 int
12364 SaveGameToFile (char *filename, int append)
12365 {
12366     FILE *f;
12367     char buf[MSG_SIZ];
12368     int result, i, t,tot=0;
12369
12370     if (strcmp(filename, "-") == 0) {
12371         return SaveGame(stdout, 0, NULL);
12372     } else {
12373         for(i=0; i<10; i++) { // upto 10 tries
12374              f = fopen(filename, append ? "a" : "w");
12375              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12376              if(f || errno != 13) break;
12377              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12378              tot += t;
12379         }
12380         if (f == NULL) {
12381             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12382             DisplayError(buf, errno);
12383             return FALSE;
12384         } else {
12385             safeStrCpy(buf, lastMsg, MSG_SIZ);
12386             DisplayMessage(_("Waiting for access to save file"), "");
12387             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12388             DisplayMessage(_("Saving game"), "");
12389             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12390             result = SaveGame(f, 0, NULL);
12391             DisplayMessage(buf, "");
12392             return result;
12393         }
12394     }
12395 }
12396
12397 char *
12398 SavePart (char *str)
12399 {
12400     static char buf[MSG_SIZ];
12401     char *p;
12402
12403     p = strchr(str, ' ');
12404     if (p == NULL) return str;
12405     strncpy(buf, str, p - str);
12406     buf[p - str] = NULLCHAR;
12407     return buf;
12408 }
12409
12410 #define PGN_MAX_LINE 75
12411
12412 #define PGN_SIDE_WHITE  0
12413 #define PGN_SIDE_BLACK  1
12414
12415 static int
12416 FindFirstMoveOutOfBook (int side)
12417 {
12418     int result = -1;
12419
12420     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12421         int index = backwardMostMove;
12422         int has_book_hit = 0;
12423
12424         if( (index % 2) != side ) {
12425             index++;
12426         }
12427
12428         while( index < forwardMostMove ) {
12429             /* Check to see if engine is in book */
12430             int depth = pvInfoList[index].depth;
12431             int score = pvInfoList[index].score;
12432             int in_book = 0;
12433
12434             if( depth <= 2 ) {
12435                 in_book = 1;
12436             }
12437             else if( score == 0 && depth == 63 ) {
12438                 in_book = 1; /* Zappa */
12439             }
12440             else if( score == 2 && depth == 99 ) {
12441                 in_book = 1; /* Abrok */
12442             }
12443
12444             has_book_hit += in_book;
12445
12446             if( ! in_book ) {
12447                 result = index;
12448
12449                 break;
12450             }
12451
12452             index += 2;
12453         }
12454     }
12455
12456     return result;
12457 }
12458
12459 void
12460 GetOutOfBookInfo (char * buf)
12461 {
12462     int oob[2];
12463     int i;
12464     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12465
12466     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12467     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12468
12469     *buf = '\0';
12470
12471     if( oob[0] >= 0 || oob[1] >= 0 ) {
12472         for( i=0; i<2; i++ ) {
12473             int idx = oob[i];
12474
12475             if( idx >= 0 ) {
12476                 if( i > 0 && oob[0] >= 0 ) {
12477                     strcat( buf, "   " );
12478                 }
12479
12480                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12481                 sprintf( buf+strlen(buf), "%s%.2f",
12482                     pvInfoList[idx].score >= 0 ? "+" : "",
12483                     pvInfoList[idx].score / 100.0 );
12484             }
12485         }
12486     }
12487 }
12488
12489 /* Save game in PGN style and close the file */
12490 int
12491 SaveGamePGN (FILE *f)
12492 {
12493     int i, offset, linelen, newblock;
12494     time_t tm;
12495 //    char *movetext;
12496     char numtext[32];
12497     int movelen, numlen, blank;
12498     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12499
12500     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12501
12502     tm = time((time_t *) NULL);
12503
12504     PrintPGNTags(f, &gameInfo);
12505
12506     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12507
12508     if (backwardMostMove > 0 || startedFromSetupPosition) {
12509         char *fen = PositionToFEN(backwardMostMove, NULL);
12510         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12511         fprintf(f, "\n{--------------\n");
12512         PrintPosition(f, backwardMostMove);
12513         fprintf(f, "--------------}\n");
12514         free(fen);
12515     }
12516     else {
12517         /* [AS] Out of book annotation */
12518         if( appData.saveOutOfBookInfo ) {
12519             char buf[64];
12520
12521             GetOutOfBookInfo( buf );
12522
12523             if( buf[0] != '\0' ) {
12524                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12525             }
12526         }
12527
12528         fprintf(f, "\n");
12529     }
12530
12531     i = backwardMostMove;
12532     linelen = 0;
12533     newblock = TRUE;
12534
12535     while (i < forwardMostMove) {
12536         /* Print comments preceding this move */
12537         if (commentList[i] != NULL) {
12538             if (linelen > 0) fprintf(f, "\n");
12539             fprintf(f, "%s", commentList[i]);
12540             linelen = 0;
12541             newblock = TRUE;
12542         }
12543
12544         /* Format move number */
12545         if ((i % 2) == 0)
12546           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12547         else
12548           if (newblock)
12549             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12550           else
12551             numtext[0] = NULLCHAR;
12552
12553         numlen = strlen(numtext);
12554         newblock = FALSE;
12555
12556         /* Print move number */
12557         blank = linelen > 0 && numlen > 0;
12558         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12559             fprintf(f, "\n");
12560             linelen = 0;
12561             blank = 0;
12562         }
12563         if (blank) {
12564             fprintf(f, " ");
12565             linelen++;
12566         }
12567         fprintf(f, "%s", numtext);
12568         linelen += numlen;
12569
12570         /* Get move */
12571         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12572         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12573
12574         /* Print move */
12575         blank = linelen > 0 && movelen > 0;
12576         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12577             fprintf(f, "\n");
12578             linelen = 0;
12579             blank = 0;
12580         }
12581         if (blank) {
12582             fprintf(f, " ");
12583             linelen++;
12584         }
12585         fprintf(f, "%s", move_buffer);
12586         linelen += movelen;
12587
12588         /* [AS] Add PV info if present */
12589         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12590             /* [HGM] add time */
12591             char buf[MSG_SIZ]; int seconds;
12592
12593             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12594
12595             if( seconds <= 0)
12596               buf[0] = 0;
12597             else
12598               if( seconds < 30 )
12599                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12600               else
12601                 {
12602                   seconds = (seconds + 4)/10; // round to full seconds
12603                   if( seconds < 60 )
12604                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12605                   else
12606                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12607                 }
12608
12609             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12610                       pvInfoList[i].score >= 0 ? "+" : "",
12611                       pvInfoList[i].score / 100.0,
12612                       pvInfoList[i].depth,
12613                       buf );
12614
12615             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12616
12617             /* Print score/depth */
12618             blank = linelen > 0 && movelen > 0;
12619             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12620                 fprintf(f, "\n");
12621                 linelen = 0;
12622                 blank = 0;
12623             }
12624             if (blank) {
12625                 fprintf(f, " ");
12626                 linelen++;
12627             }
12628             fprintf(f, "%s", move_buffer);
12629             linelen += movelen;
12630         }
12631
12632         i++;
12633     }
12634
12635     /* Start a new line */
12636     if (linelen > 0) fprintf(f, "\n");
12637
12638     /* Print comments after last move */
12639     if (commentList[i] != NULL) {
12640         fprintf(f, "%s\n", commentList[i]);
12641     }
12642
12643     /* Print result */
12644     if (gameInfo.resultDetails != NULL &&
12645         gameInfo.resultDetails[0] != NULLCHAR) {
12646         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12647                 PGNResult(gameInfo.result));
12648     } else {
12649         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12650     }
12651
12652     fclose(f);
12653     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12654     return TRUE;
12655 }
12656
12657 /* Save game in old style and close the file */
12658 int
12659 SaveGameOldStyle (FILE *f)
12660 {
12661     int i, offset;
12662     time_t tm;
12663
12664     tm = time((time_t *) NULL);
12665
12666     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12667     PrintOpponents(f);
12668
12669     if (backwardMostMove > 0 || startedFromSetupPosition) {
12670         fprintf(f, "\n[--------------\n");
12671         PrintPosition(f, backwardMostMove);
12672         fprintf(f, "--------------]\n");
12673     } else {
12674         fprintf(f, "\n");
12675     }
12676
12677     i = backwardMostMove;
12678     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12679
12680     while (i < forwardMostMove) {
12681         if (commentList[i] != NULL) {
12682             fprintf(f, "[%s]\n", commentList[i]);
12683         }
12684
12685         if ((i % 2) == 1) {
12686             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12687             i++;
12688         } else {
12689             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12690             i++;
12691             if (commentList[i] != NULL) {
12692                 fprintf(f, "\n");
12693                 continue;
12694             }
12695             if (i >= forwardMostMove) {
12696                 fprintf(f, "\n");
12697                 break;
12698             }
12699             fprintf(f, "%s\n", parseList[i]);
12700             i++;
12701         }
12702     }
12703
12704     if (commentList[i] != NULL) {
12705         fprintf(f, "[%s]\n", commentList[i]);
12706     }
12707
12708     /* This isn't really the old style, but it's close enough */
12709     if (gameInfo.resultDetails != NULL &&
12710         gameInfo.resultDetails[0] != NULLCHAR) {
12711         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12712                 gameInfo.resultDetails);
12713     } else {
12714         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12715     }
12716
12717     fclose(f);
12718     return TRUE;
12719 }
12720
12721 /* Save the current game to open file f and close the file */
12722 int
12723 SaveGame (FILE *f, int dummy, char *dummy2)
12724 {
12725     if (gameMode == EditPosition) EditPositionDone(TRUE);
12726     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12727     if (appData.oldSaveStyle)
12728       return SaveGameOldStyle(f);
12729     else
12730       return SaveGamePGN(f);
12731 }
12732
12733 /* Save the current position to the given file */
12734 int
12735 SavePositionToFile (char *filename)
12736 {
12737     FILE *f;
12738     char buf[MSG_SIZ];
12739
12740     if (strcmp(filename, "-") == 0) {
12741         return SavePosition(stdout, 0, NULL);
12742     } else {
12743         f = fopen(filename, "a");
12744         if (f == NULL) {
12745             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12746             DisplayError(buf, errno);
12747             return FALSE;
12748         } else {
12749             safeStrCpy(buf, lastMsg, MSG_SIZ);
12750             DisplayMessage(_("Waiting for access to save file"), "");
12751             flock(fileno(f), LOCK_EX); // [HGM] lock
12752             DisplayMessage(_("Saving position"), "");
12753             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12754             SavePosition(f, 0, NULL);
12755             DisplayMessage(buf, "");
12756             return TRUE;
12757         }
12758     }
12759 }
12760
12761 /* Save the current position to the given open file and close the file */
12762 int
12763 SavePosition (FILE *f, int dummy, char *dummy2)
12764 {
12765     time_t tm;
12766     char *fen;
12767
12768     if (gameMode == EditPosition) EditPositionDone(TRUE);
12769     if (appData.oldSaveStyle) {
12770         tm = time((time_t *) NULL);
12771
12772         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12773         PrintOpponents(f);
12774         fprintf(f, "[--------------\n");
12775         PrintPosition(f, currentMove);
12776         fprintf(f, "--------------]\n");
12777     } else {
12778         fen = PositionToFEN(currentMove, NULL);
12779         fprintf(f, "%s\n", fen);
12780         free(fen);
12781     }
12782     fclose(f);
12783     return TRUE;
12784 }
12785
12786 void
12787 ReloadCmailMsgEvent (int unregister)
12788 {
12789 #if !WIN32
12790     static char *inFilename = NULL;
12791     static char *outFilename;
12792     int i;
12793     struct stat inbuf, outbuf;
12794     int status;
12795
12796     /* Any registered moves are unregistered if unregister is set, */
12797     /* i.e. invoked by the signal handler */
12798     if (unregister) {
12799         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12800             cmailMoveRegistered[i] = FALSE;
12801             if (cmailCommentList[i] != NULL) {
12802                 free(cmailCommentList[i]);
12803                 cmailCommentList[i] = NULL;
12804             }
12805         }
12806         nCmailMovesRegistered = 0;
12807     }
12808
12809     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12810         cmailResult[i] = CMAIL_NOT_RESULT;
12811     }
12812     nCmailResults = 0;
12813
12814     if (inFilename == NULL) {
12815         /* Because the filenames are static they only get malloced once  */
12816         /* and they never get freed                                      */
12817         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12818         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12819
12820         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12821         sprintf(outFilename, "%s.out", appData.cmailGameName);
12822     }
12823
12824     status = stat(outFilename, &outbuf);
12825     if (status < 0) {
12826         cmailMailedMove = FALSE;
12827     } else {
12828         status = stat(inFilename, &inbuf);
12829         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12830     }
12831
12832     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12833        counts the games, notes how each one terminated, etc.
12834
12835        It would be nice to remove this kludge and instead gather all
12836        the information while building the game list.  (And to keep it
12837        in the game list nodes instead of having a bunch of fixed-size
12838        parallel arrays.)  Note this will require getting each game's
12839        termination from the PGN tags, as the game list builder does
12840        not process the game moves.  --mann
12841        */
12842     cmailMsgLoaded = TRUE;
12843     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12844
12845     /* Load first game in the file or popup game menu */
12846     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12847
12848 #endif /* !WIN32 */
12849     return;
12850 }
12851
12852 int
12853 RegisterMove ()
12854 {
12855     FILE *f;
12856     char string[MSG_SIZ];
12857
12858     if (   cmailMailedMove
12859         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12860         return TRUE;            /* Allow free viewing  */
12861     }
12862
12863     /* Unregister move to ensure that we don't leave RegisterMove        */
12864     /* with the move registered when the conditions for registering no   */
12865     /* longer hold                                                       */
12866     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12867         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12868         nCmailMovesRegistered --;
12869
12870         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12871           {
12872               free(cmailCommentList[lastLoadGameNumber - 1]);
12873               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12874           }
12875     }
12876
12877     if (cmailOldMove == -1) {
12878         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12879         return FALSE;
12880     }
12881
12882     if (currentMove > cmailOldMove + 1) {
12883         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12884         return FALSE;
12885     }
12886
12887     if (currentMove < cmailOldMove) {
12888         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12889         return FALSE;
12890     }
12891
12892     if (forwardMostMove > currentMove) {
12893         /* Silently truncate extra moves */
12894         TruncateGame();
12895     }
12896
12897     if (   (currentMove == cmailOldMove + 1)
12898         || (   (currentMove == cmailOldMove)
12899             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12900                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12901         if (gameInfo.result != GameUnfinished) {
12902             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12903         }
12904
12905         if (commentList[currentMove] != NULL) {
12906             cmailCommentList[lastLoadGameNumber - 1]
12907               = StrSave(commentList[currentMove]);
12908         }
12909         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12910
12911         if (appData.debugMode)
12912           fprintf(debugFP, "Saving %s for game %d\n",
12913                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12914
12915         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12916
12917         f = fopen(string, "w");
12918         if (appData.oldSaveStyle) {
12919             SaveGameOldStyle(f); /* also closes the file */
12920
12921             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12922             f = fopen(string, "w");
12923             SavePosition(f, 0, NULL); /* also closes the file */
12924         } else {
12925             fprintf(f, "{--------------\n");
12926             PrintPosition(f, currentMove);
12927             fprintf(f, "--------------}\n\n");
12928
12929             SaveGame(f, 0, NULL); /* also closes the file*/
12930         }
12931
12932         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12933         nCmailMovesRegistered ++;
12934     } else if (nCmailGames == 1) {
12935         DisplayError(_("You have not made a move yet"), 0);
12936         return FALSE;
12937     }
12938
12939     return TRUE;
12940 }
12941
12942 void
12943 MailMoveEvent ()
12944 {
12945 #if !WIN32
12946     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12947     FILE *commandOutput;
12948     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12949     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12950     int nBuffers;
12951     int i;
12952     int archived;
12953     char *arcDir;
12954
12955     if (! cmailMsgLoaded) {
12956         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12957         return;
12958     }
12959
12960     if (nCmailGames == nCmailResults) {
12961         DisplayError(_("No unfinished games"), 0);
12962         return;
12963     }
12964
12965 #if CMAIL_PROHIBIT_REMAIL
12966     if (cmailMailedMove) {
12967       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);
12968         DisplayError(msg, 0);
12969         return;
12970     }
12971 #endif
12972
12973     if (! (cmailMailedMove || RegisterMove())) return;
12974
12975     if (   cmailMailedMove
12976         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12977       snprintf(string, MSG_SIZ, partCommandString,
12978                appData.debugMode ? " -v" : "", appData.cmailGameName);
12979         commandOutput = popen(string, "r");
12980
12981         if (commandOutput == NULL) {
12982             DisplayError(_("Failed to invoke cmail"), 0);
12983         } else {
12984             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12985                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12986             }
12987             if (nBuffers > 1) {
12988                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12989                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12990                 nBytes = MSG_SIZ - 1;
12991             } else {
12992                 (void) memcpy(msg, buffer, nBytes);
12993             }
12994             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12995
12996             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12997                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12998
12999                 archived = TRUE;
13000                 for (i = 0; i < nCmailGames; i ++) {
13001                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13002                         archived = FALSE;
13003                     }
13004                 }
13005                 if (   archived
13006                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13007                         != NULL)) {
13008                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13009                            arcDir,
13010                            appData.cmailGameName,
13011                            gameInfo.date);
13012                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13013                     cmailMsgLoaded = FALSE;
13014                 }
13015             }
13016
13017             DisplayInformation(msg);
13018             pclose(commandOutput);
13019         }
13020     } else {
13021         if ((*cmailMsg) != '\0') {
13022             DisplayInformation(cmailMsg);
13023         }
13024     }
13025
13026     return;
13027 #endif /* !WIN32 */
13028 }
13029
13030 char *
13031 CmailMsg ()
13032 {
13033 #if WIN32
13034     return NULL;
13035 #else
13036     int  prependComma = 0;
13037     char number[5];
13038     char string[MSG_SIZ];       /* Space for game-list */
13039     int  i;
13040
13041     if (!cmailMsgLoaded) return "";
13042
13043     if (cmailMailedMove) {
13044       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13045     } else {
13046         /* Create a list of games left */
13047       snprintf(string, MSG_SIZ, "[");
13048         for (i = 0; i < nCmailGames; i ++) {
13049             if (! (   cmailMoveRegistered[i]
13050                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13051                 if (prependComma) {
13052                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13053                 } else {
13054                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13055                     prependComma = 1;
13056                 }
13057
13058                 strcat(string, number);
13059             }
13060         }
13061         strcat(string, "]");
13062
13063         if (nCmailMovesRegistered + nCmailResults == 0) {
13064             switch (nCmailGames) {
13065               case 1:
13066                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13067                 break;
13068
13069               case 2:
13070                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13071                 break;
13072
13073               default:
13074                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13075                          nCmailGames);
13076                 break;
13077             }
13078         } else {
13079             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13080               case 1:
13081                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13082                          string);
13083                 break;
13084
13085               case 0:
13086                 if (nCmailResults == nCmailGames) {
13087                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13088                 } else {
13089                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13090                 }
13091                 break;
13092
13093               default:
13094                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13095                          string);
13096             }
13097         }
13098     }
13099     return cmailMsg;
13100 #endif /* WIN32 */
13101 }
13102
13103 void
13104 ResetGameEvent ()
13105 {
13106     if (gameMode == Training)
13107       SetTrainingModeOff();
13108
13109     Reset(TRUE, TRUE);
13110     cmailMsgLoaded = FALSE;
13111     if (appData.icsActive) {
13112       SendToICS(ics_prefix);
13113       SendToICS("refresh\n");
13114     }
13115 }
13116
13117 void
13118 ExitEvent (int status)
13119 {
13120     exiting++;
13121     if (exiting > 2) {
13122       /* Give up on clean exit */
13123       exit(status);
13124     }
13125     if (exiting > 1) {
13126       /* Keep trying for clean exit */
13127       return;
13128     }
13129
13130     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13131
13132     if (telnetISR != NULL) {
13133       RemoveInputSource(telnetISR);
13134     }
13135     if (icsPR != NoProc) {
13136       DestroyChildProcess(icsPR, TRUE);
13137     }
13138
13139     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13140     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13141
13142     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13143     /* make sure this other one finishes before killing it!                  */
13144     if(endingGame) { int count = 0;
13145         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13146         while(endingGame && count++ < 10) DoSleep(1);
13147         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13148     }
13149
13150     /* Kill off chess programs */
13151     if (first.pr != NoProc) {
13152         ExitAnalyzeMode();
13153
13154         DoSleep( appData.delayBeforeQuit );
13155         SendToProgram("quit\n", &first);
13156         DoSleep( appData.delayAfterQuit );
13157         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13158     }
13159     if (second.pr != NoProc) {
13160         DoSleep( appData.delayBeforeQuit );
13161         SendToProgram("quit\n", &second);
13162         DoSleep( appData.delayAfterQuit );
13163         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13164     }
13165     if (first.isr != NULL) {
13166         RemoveInputSource(first.isr);
13167     }
13168     if (second.isr != NULL) {
13169         RemoveInputSource(second.isr);
13170     }
13171
13172     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13173     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13174
13175     ShutDownFrontEnd();
13176     exit(status);
13177 }
13178
13179 void
13180 PauseEvent ()
13181 {
13182     if (appData.debugMode)
13183         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13184     if (pausing) {
13185         pausing = FALSE;
13186         ModeHighlight();
13187         if (gameMode == MachinePlaysWhite ||
13188             gameMode == MachinePlaysBlack) {
13189             StartClocks();
13190         } else {
13191             DisplayBothClocks();
13192         }
13193         if (gameMode == PlayFromGameFile) {
13194             if (appData.timeDelay >= 0)
13195                 AutoPlayGameLoop();
13196         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13197             Reset(FALSE, TRUE);
13198             SendToICS(ics_prefix);
13199             SendToICS("refresh\n");
13200         } else if (currentMove < forwardMostMove) {
13201             ForwardInner(forwardMostMove);
13202         }
13203         pauseExamInvalid = FALSE;
13204     } else {
13205         switch (gameMode) {
13206           default:
13207             return;
13208           case IcsExamining:
13209             pauseExamForwardMostMove = forwardMostMove;
13210             pauseExamInvalid = FALSE;
13211             /* fall through */
13212           case IcsObserving:
13213           case IcsPlayingWhite:
13214           case IcsPlayingBlack:
13215             pausing = TRUE;
13216             ModeHighlight();
13217             return;
13218           case PlayFromGameFile:
13219             (void) StopLoadGameTimer();
13220             pausing = TRUE;
13221             ModeHighlight();
13222             break;
13223           case BeginningOfGame:
13224             if (appData.icsActive) return;
13225             /* else fall through */
13226           case MachinePlaysWhite:
13227           case MachinePlaysBlack:
13228           case TwoMachinesPlay:
13229             if (forwardMostMove == 0)
13230               return;           /* don't pause if no one has moved */
13231             if ((gameMode == MachinePlaysWhite &&
13232                  !WhiteOnMove(forwardMostMove)) ||
13233                 (gameMode == MachinePlaysBlack &&
13234                  WhiteOnMove(forwardMostMove))) {
13235                 StopClocks();
13236             }
13237             pausing = TRUE;
13238             ModeHighlight();
13239             break;
13240         }
13241     }
13242 }
13243
13244 void
13245 EditCommentEvent ()
13246 {
13247     char title[MSG_SIZ];
13248
13249     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13250       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13251     } else {
13252       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13253                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13254                parseList[currentMove - 1]);
13255     }
13256
13257     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13258 }
13259
13260
13261 void
13262 EditTagsEvent ()
13263 {
13264     char *tags = PGNTags(&gameInfo);
13265     bookUp = FALSE;
13266     EditTagsPopUp(tags, NULL);
13267     free(tags);
13268 }
13269
13270 void
13271 AnalyzeModeEvent ()
13272 {
13273     if (appData.noChessProgram || gameMode == AnalyzeMode)
13274       return;
13275
13276     if (gameMode != AnalyzeFile) {
13277         if (!appData.icsEngineAnalyze) {
13278                EditGameEvent();
13279                if (gameMode != EditGame) return;
13280         }
13281         ResurrectChessProgram();
13282         SendToProgram("analyze\n", &first);
13283         first.analyzing = TRUE;
13284         /*first.maybeThinking = TRUE;*/
13285         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13286         EngineOutputPopUp();
13287     }
13288     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13289     pausing = FALSE;
13290     ModeHighlight();
13291     SetGameInfo();
13292
13293     StartAnalysisClock();
13294     GetTimeMark(&lastNodeCountTime);
13295     lastNodeCount = 0;
13296 }
13297
13298 void
13299 AnalyzeFileEvent ()
13300 {
13301     if (appData.noChessProgram || gameMode == AnalyzeFile)
13302       return;
13303
13304     if (gameMode != AnalyzeMode) {
13305         EditGameEvent();
13306         if (gameMode != EditGame) return;
13307         ResurrectChessProgram();
13308         SendToProgram("analyze\n", &first);
13309         first.analyzing = TRUE;
13310         /*first.maybeThinking = TRUE;*/
13311         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13312         EngineOutputPopUp();
13313     }
13314     gameMode = AnalyzeFile;
13315     pausing = FALSE;
13316     ModeHighlight();
13317     SetGameInfo();
13318
13319     StartAnalysisClock();
13320     GetTimeMark(&lastNodeCountTime);
13321     lastNodeCount = 0;
13322     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13323 }
13324
13325 void
13326 MachineWhiteEvent ()
13327 {
13328     char buf[MSG_SIZ];
13329     char *bookHit = NULL;
13330
13331     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13332       return;
13333
13334
13335     if (gameMode == PlayFromGameFile ||
13336         gameMode == TwoMachinesPlay  ||
13337         gameMode == Training         ||
13338         gameMode == AnalyzeMode      ||
13339         gameMode == EndOfGame)
13340         EditGameEvent();
13341
13342     if (gameMode == EditPosition)
13343         EditPositionDone(TRUE);
13344
13345     if (!WhiteOnMove(currentMove)) {
13346         DisplayError(_("It is not White's turn"), 0);
13347         return;
13348     }
13349
13350     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13351       ExitAnalyzeMode();
13352
13353     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13354         gameMode == AnalyzeFile)
13355         TruncateGame();
13356
13357     ResurrectChessProgram();    /* in case it isn't running */
13358     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13359         gameMode = MachinePlaysWhite;
13360         ResetClocks();
13361     } else
13362     gameMode = MachinePlaysWhite;
13363     pausing = FALSE;
13364     ModeHighlight();
13365     SetGameInfo();
13366     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13367     DisplayTitle(buf);
13368     if (first.sendName) {
13369       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13370       SendToProgram(buf, &first);
13371     }
13372     if (first.sendTime) {
13373       if (first.useColors) {
13374         SendToProgram("black\n", &first); /*gnu kludge*/
13375       }
13376       SendTimeRemaining(&first, TRUE);
13377     }
13378     if (first.useColors) {
13379       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13380     }
13381     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13382     SetMachineThinkingEnables();
13383     first.maybeThinking = TRUE;
13384     StartClocks();
13385     firstMove = FALSE;
13386
13387     if (appData.autoFlipView && !flipView) {
13388       flipView = !flipView;
13389       DrawPosition(FALSE, NULL);
13390       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13391     }
13392
13393     if(bookHit) { // [HGM] book: simulate book reply
13394         static char bookMove[MSG_SIZ]; // a bit generous?
13395
13396         programStats.nodes = programStats.depth = programStats.time =
13397         programStats.score = programStats.got_only_move = 0;
13398         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13399
13400         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13401         strcat(bookMove, bookHit);
13402         HandleMachineMove(bookMove, &first);
13403     }
13404 }
13405
13406 void
13407 MachineBlackEvent ()
13408 {
13409   char buf[MSG_SIZ];
13410   char *bookHit = NULL;
13411
13412     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13413         return;
13414
13415
13416     if (gameMode == PlayFromGameFile ||
13417         gameMode == TwoMachinesPlay  ||
13418         gameMode == Training         ||
13419         gameMode == AnalyzeMode      ||
13420         gameMode == EndOfGame)
13421         EditGameEvent();
13422
13423     if (gameMode == EditPosition)
13424         EditPositionDone(TRUE);
13425
13426     if (WhiteOnMove(currentMove)) {
13427         DisplayError(_("It is not Black's turn"), 0);
13428         return;
13429     }
13430
13431     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13432       ExitAnalyzeMode();
13433
13434     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13435         gameMode == AnalyzeFile)
13436         TruncateGame();
13437
13438     ResurrectChessProgram();    /* in case it isn't running */
13439     gameMode = MachinePlaysBlack;
13440     pausing = FALSE;
13441     ModeHighlight();
13442     SetGameInfo();
13443     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13444     DisplayTitle(buf);
13445     if (first.sendName) {
13446       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13447       SendToProgram(buf, &first);
13448     }
13449     if (first.sendTime) {
13450       if (first.useColors) {
13451         SendToProgram("white\n", &first); /*gnu kludge*/
13452       }
13453       SendTimeRemaining(&first, FALSE);
13454     }
13455     if (first.useColors) {
13456       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13457     }
13458     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13459     SetMachineThinkingEnables();
13460     first.maybeThinking = TRUE;
13461     StartClocks();
13462
13463     if (appData.autoFlipView && flipView) {
13464       flipView = !flipView;
13465       DrawPosition(FALSE, NULL);
13466       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13467     }
13468     if(bookHit) { // [HGM] book: simulate book reply
13469         static char bookMove[MSG_SIZ]; // a bit generous?
13470
13471         programStats.nodes = programStats.depth = programStats.time =
13472         programStats.score = programStats.got_only_move = 0;
13473         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13474
13475         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13476         strcat(bookMove, bookHit);
13477         HandleMachineMove(bookMove, &first);
13478     }
13479 }
13480
13481
13482 void
13483 DisplayTwoMachinesTitle ()
13484 {
13485     char buf[MSG_SIZ];
13486     if (appData.matchGames > 0) {
13487         if(appData.tourneyFile[0]) {
13488           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13489                    gameInfo.white, _("vs."), gameInfo.black,
13490                    nextGame+1, appData.matchGames+1,
13491                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13492         } else 
13493         if (first.twoMachinesColor[0] == 'w') {
13494           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13495                    gameInfo.white, _("vs."),  gameInfo.black,
13496                    first.matchWins, second.matchWins,
13497                    matchGame - 1 - (first.matchWins + second.matchWins));
13498         } else {
13499           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13500                    gameInfo.white, _("vs."), gameInfo.black,
13501                    second.matchWins, first.matchWins,
13502                    matchGame - 1 - (first.matchWins + second.matchWins));
13503         }
13504     } else {
13505       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13506     }
13507     DisplayTitle(buf);
13508 }
13509
13510 void
13511 SettingsMenuIfReady ()
13512 {
13513   if (second.lastPing != second.lastPong) {
13514     DisplayMessage("", _("Waiting for second chess program"));
13515     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13516     return;
13517   }
13518   ThawUI();
13519   DisplayMessage("", "");
13520   SettingsPopUp(&second);
13521 }
13522
13523 int
13524 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13525 {
13526     char buf[MSG_SIZ];
13527     if (cps->pr == NoProc) {
13528         StartChessProgram(cps);
13529         if (cps->protocolVersion == 1) {
13530           retry();
13531         } else {
13532           /* kludge: allow timeout for initial "feature" command */
13533           FreezeUI();
13534           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13535           DisplayMessage("", buf);
13536           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13537         }
13538         return 1;
13539     }
13540     return 0;
13541 }
13542
13543 void
13544 TwoMachinesEvent P((void))
13545 {
13546     int i;
13547     char buf[MSG_SIZ];
13548     ChessProgramState *onmove;
13549     char *bookHit = NULL;
13550     static int stalling = 0;
13551     TimeMark now;
13552     long wait;
13553
13554     if (appData.noChessProgram) return;
13555
13556     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13557         DisplayError("second engine does not play this", 0);
13558         return;
13559     }
13560
13561     switch (gameMode) {
13562       case TwoMachinesPlay:
13563         return;
13564       case MachinePlaysWhite:
13565       case MachinePlaysBlack:
13566         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13567             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13568             return;
13569         }
13570         /* fall through */
13571       case BeginningOfGame:
13572       case PlayFromGameFile:
13573       case EndOfGame:
13574         EditGameEvent();
13575         if (gameMode != EditGame) return;
13576         break;
13577       case EditPosition:
13578         EditPositionDone(TRUE);
13579         break;
13580       case AnalyzeMode:
13581       case AnalyzeFile:
13582         ExitAnalyzeMode();
13583         break;
13584       case EditGame:
13585       default:
13586         break;
13587     }
13588
13589 //    forwardMostMove = currentMove;
13590     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13591
13592     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13593
13594     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13595     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13596       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13597       return;
13598     }
13599     if(!stalling) {
13600       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13601       SendToProgram("force\n", &second);
13602       stalling = 1;
13603       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13604       return;
13605     }
13606     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13607     if(appData.matchPause>10000 || appData.matchPause<10)
13608                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13609     wait = SubtractTimeMarks(&now, &pauseStart);
13610     if(wait < appData.matchPause) {
13611         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13612         return;
13613     }
13614     // we are now committed to starting the game
13615     stalling = 0;
13616     DisplayMessage("", "");
13617     if (startedFromSetupPosition) {
13618         SendBoard(&second, backwardMostMove);
13619     if (appData.debugMode) {
13620         fprintf(debugFP, "Two Machines\n");
13621     }
13622     }
13623     for (i = backwardMostMove; i < forwardMostMove; i++) {
13624         SendMoveToProgram(i, &second);
13625     }
13626
13627     gameMode = TwoMachinesPlay;
13628     pausing = FALSE;
13629     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13630     SetGameInfo();
13631     DisplayTwoMachinesTitle();
13632     firstMove = TRUE;
13633     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13634         onmove = &first;
13635     } else {
13636         onmove = &second;
13637     }
13638     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13639     SendToProgram(first.computerString, &first);
13640     if (first.sendName) {
13641       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13642       SendToProgram(buf, &first);
13643     }
13644     SendToProgram(second.computerString, &second);
13645     if (second.sendName) {
13646       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13647       SendToProgram(buf, &second);
13648     }
13649
13650     ResetClocks();
13651     if (!first.sendTime || !second.sendTime) {
13652         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13653         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13654     }
13655     if (onmove->sendTime) {
13656       if (onmove->useColors) {
13657         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13658       }
13659       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13660     }
13661     if (onmove->useColors) {
13662       SendToProgram(onmove->twoMachinesColor, onmove);
13663     }
13664     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13665 //    SendToProgram("go\n", onmove);
13666     onmove->maybeThinking = TRUE;
13667     SetMachineThinkingEnables();
13668
13669     StartClocks();
13670
13671     if(bookHit) { // [HGM] book: simulate book reply
13672         static char bookMove[MSG_SIZ]; // a bit generous?
13673
13674         programStats.nodes = programStats.depth = programStats.time =
13675         programStats.score = programStats.got_only_move = 0;
13676         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13677
13678         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13679         strcat(bookMove, bookHit);
13680         savedMessage = bookMove; // args for deferred call
13681         savedState = onmove;
13682         ScheduleDelayedEvent(DeferredBookMove, 1);
13683     }
13684 }
13685
13686 void
13687 TrainingEvent ()
13688 {
13689     if (gameMode == Training) {
13690       SetTrainingModeOff();
13691       gameMode = PlayFromGameFile;
13692       DisplayMessage("", _("Training mode off"));
13693     } else {
13694       gameMode = Training;
13695       animateTraining = appData.animate;
13696
13697       /* make sure we are not already at the end of the game */
13698       if (currentMove < forwardMostMove) {
13699         SetTrainingModeOn();
13700         DisplayMessage("", _("Training mode on"));
13701       } else {
13702         gameMode = PlayFromGameFile;
13703         DisplayError(_("Already at end of game"), 0);
13704       }
13705     }
13706     ModeHighlight();
13707 }
13708
13709 void
13710 IcsClientEvent ()
13711 {
13712     if (!appData.icsActive) return;
13713     switch (gameMode) {
13714       case IcsPlayingWhite:
13715       case IcsPlayingBlack:
13716       case IcsObserving:
13717       case IcsIdle:
13718       case BeginningOfGame:
13719       case IcsExamining:
13720         return;
13721
13722       case EditGame:
13723         break;
13724
13725       case EditPosition:
13726         EditPositionDone(TRUE);
13727         break;
13728
13729       case AnalyzeMode:
13730       case AnalyzeFile:
13731         ExitAnalyzeMode();
13732         break;
13733
13734       default:
13735         EditGameEvent();
13736         break;
13737     }
13738
13739     gameMode = IcsIdle;
13740     ModeHighlight();
13741     return;
13742 }
13743
13744 void
13745 EditGameEvent ()
13746 {
13747     int i;
13748
13749     switch (gameMode) {
13750       case Training:
13751         SetTrainingModeOff();
13752         break;
13753       case MachinePlaysWhite:
13754       case MachinePlaysBlack:
13755       case BeginningOfGame:
13756         SendToProgram("force\n", &first);
13757         SetUserThinkingEnables();
13758         break;
13759       case PlayFromGameFile:
13760         (void) StopLoadGameTimer();
13761         if (gameFileFP != NULL) {
13762             gameFileFP = NULL;
13763         }
13764         break;
13765       case EditPosition:
13766         EditPositionDone(TRUE);
13767         break;
13768       case AnalyzeMode:
13769       case AnalyzeFile:
13770         ExitAnalyzeMode();
13771         SendToProgram("force\n", &first);
13772         break;
13773       case TwoMachinesPlay:
13774         GameEnds(EndOfFile, NULL, GE_PLAYER);
13775         ResurrectChessProgram();
13776         SetUserThinkingEnables();
13777         break;
13778       case EndOfGame:
13779         ResurrectChessProgram();
13780         break;
13781       case IcsPlayingBlack:
13782       case IcsPlayingWhite:
13783         DisplayError(_("Warning: You are still playing a game"), 0);
13784         break;
13785       case IcsObserving:
13786         DisplayError(_("Warning: You are still observing a game"), 0);
13787         break;
13788       case IcsExamining:
13789         DisplayError(_("Warning: You are still examining a game"), 0);
13790         break;
13791       case IcsIdle:
13792         break;
13793       case EditGame:
13794       default:
13795         return;
13796     }
13797
13798     pausing = FALSE;
13799     StopClocks();
13800     first.offeredDraw = second.offeredDraw = 0;
13801
13802     if (gameMode == PlayFromGameFile) {
13803         whiteTimeRemaining = timeRemaining[0][currentMove];
13804         blackTimeRemaining = timeRemaining[1][currentMove];
13805         DisplayTitle("");
13806     }
13807
13808     if (gameMode == MachinePlaysWhite ||
13809         gameMode == MachinePlaysBlack ||
13810         gameMode == TwoMachinesPlay ||
13811         gameMode == EndOfGame) {
13812         i = forwardMostMove;
13813         while (i > currentMove) {
13814             SendToProgram("undo\n", &first);
13815             i--;
13816         }
13817         if(!adjustedClock) {
13818         whiteTimeRemaining = timeRemaining[0][currentMove];
13819         blackTimeRemaining = timeRemaining[1][currentMove];
13820         DisplayBothClocks();
13821         }
13822         if (whiteFlag || blackFlag) {
13823             whiteFlag = blackFlag = 0;
13824         }
13825         DisplayTitle("");
13826     }
13827
13828     gameMode = EditGame;
13829     ModeHighlight();
13830     SetGameInfo();
13831 }
13832
13833
13834 void
13835 EditPositionEvent ()
13836 {
13837     if (gameMode == EditPosition) {
13838         EditGameEvent();
13839         return;
13840     }
13841
13842     EditGameEvent();
13843     if (gameMode != EditGame) return;
13844
13845     gameMode = EditPosition;
13846     ModeHighlight();
13847     SetGameInfo();
13848     if (currentMove > 0)
13849       CopyBoard(boards[0], boards[currentMove]);
13850
13851     blackPlaysFirst = !WhiteOnMove(currentMove);
13852     ResetClocks();
13853     currentMove = forwardMostMove = backwardMostMove = 0;
13854     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13855     DisplayMove(-1);
13856     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13857 }
13858
13859 void
13860 ExitAnalyzeMode ()
13861 {
13862     /* [DM] icsEngineAnalyze - possible call from other functions */
13863     if (appData.icsEngineAnalyze) {
13864         appData.icsEngineAnalyze = FALSE;
13865
13866         DisplayMessage("",_("Close ICS engine analyze..."));
13867     }
13868     if (first.analysisSupport && first.analyzing) {
13869       SendToProgram("exit\n", &first);
13870       first.analyzing = FALSE;
13871     }
13872     thinkOutput[0] = NULLCHAR;
13873 }
13874
13875 void
13876 EditPositionDone (Boolean fakeRights)
13877 {
13878     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13879
13880     startedFromSetupPosition = TRUE;
13881     InitChessProgram(&first, FALSE);
13882     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13883       boards[0][EP_STATUS] = EP_NONE;
13884       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13885     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13886         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13887         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13888       } else boards[0][CASTLING][2] = NoRights;
13889     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13890         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13891         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13892       } else boards[0][CASTLING][5] = NoRights;
13893     }
13894     SendToProgram("force\n", &first);
13895     if (blackPlaysFirst) {
13896         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13897         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13898         currentMove = forwardMostMove = backwardMostMove = 1;
13899         CopyBoard(boards[1], boards[0]);
13900     } else {
13901         currentMove = forwardMostMove = backwardMostMove = 0;
13902     }
13903     SendBoard(&first, forwardMostMove);
13904     if (appData.debugMode) {
13905         fprintf(debugFP, "EditPosDone\n");
13906     }
13907     DisplayTitle("");
13908     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13909     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13910     gameMode = EditGame;
13911     ModeHighlight();
13912     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13913     ClearHighlights(); /* [AS] */
13914 }
13915
13916 /* Pause for `ms' milliseconds */
13917 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13918 void
13919 TimeDelay (long ms)
13920 {
13921     TimeMark m1, m2;
13922
13923     GetTimeMark(&m1);
13924     do {
13925         GetTimeMark(&m2);
13926     } while (SubtractTimeMarks(&m2, &m1) < ms);
13927 }
13928
13929 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13930 void
13931 SendMultiLineToICS (char *buf)
13932 {
13933     char temp[MSG_SIZ+1], *p;
13934     int len;
13935
13936     len = strlen(buf);
13937     if (len > MSG_SIZ)
13938       len = MSG_SIZ;
13939
13940     strncpy(temp, buf, len);
13941     temp[len] = 0;
13942
13943     p = temp;
13944     while (*p) {
13945         if (*p == '\n' || *p == '\r')
13946           *p = ' ';
13947         ++p;
13948     }
13949
13950     strcat(temp, "\n");
13951     SendToICS(temp);
13952     SendToPlayer(temp, strlen(temp));
13953 }
13954
13955 void
13956 SetWhiteToPlayEvent ()
13957 {
13958     if (gameMode == EditPosition) {
13959         blackPlaysFirst = FALSE;
13960         DisplayBothClocks();    /* works because currentMove is 0 */
13961     } else if (gameMode == IcsExamining) {
13962         SendToICS(ics_prefix);
13963         SendToICS("tomove white\n");
13964     }
13965 }
13966
13967 void
13968 SetBlackToPlayEvent ()
13969 {
13970     if (gameMode == EditPosition) {
13971         blackPlaysFirst = TRUE;
13972         currentMove = 1;        /* kludge */
13973         DisplayBothClocks();
13974         currentMove = 0;
13975     } else if (gameMode == IcsExamining) {
13976         SendToICS(ics_prefix);
13977         SendToICS("tomove black\n");
13978     }
13979 }
13980
13981 void
13982 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13983 {
13984     char buf[MSG_SIZ];
13985     ChessSquare piece = boards[0][y][x];
13986
13987     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13988
13989     switch (selection) {
13990       case ClearBoard:
13991         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13992             SendToICS(ics_prefix);
13993             SendToICS("bsetup clear\n");
13994         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13995             SendToICS(ics_prefix);
13996             SendToICS("clearboard\n");
13997         } else {
13998             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13999                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14000                 for (y = 0; y < BOARD_HEIGHT; y++) {
14001                     if (gameMode == IcsExamining) {
14002                         if (boards[currentMove][y][x] != EmptySquare) {
14003                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14004                                     AAA + x, ONE + y);
14005                             SendToICS(buf);
14006                         }
14007                     } else {
14008                         boards[0][y][x] = p;
14009                     }
14010                 }
14011             }
14012         }
14013         if (gameMode == EditPosition) {
14014             DrawPosition(FALSE, boards[0]);
14015         }
14016         break;
14017
14018       case WhitePlay:
14019         SetWhiteToPlayEvent();
14020         break;
14021
14022       case BlackPlay:
14023         SetBlackToPlayEvent();
14024         break;
14025
14026       case EmptySquare:
14027         if (gameMode == IcsExamining) {
14028             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14029             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14030             SendToICS(buf);
14031         } else {
14032             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14033                 if(x == BOARD_LEFT-2) {
14034                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14035                     boards[0][y][1] = 0;
14036                 } else
14037                 if(x == BOARD_RGHT+1) {
14038                     if(y >= gameInfo.holdingsSize) break;
14039                     boards[0][y][BOARD_WIDTH-2] = 0;
14040                 } else break;
14041             }
14042             boards[0][y][x] = EmptySquare;
14043             DrawPosition(FALSE, boards[0]);
14044         }
14045         break;
14046
14047       case PromotePiece:
14048         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14049            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14050             selection = (ChessSquare) (PROMOTED piece);
14051         } else if(piece == EmptySquare) selection = WhiteSilver;
14052         else selection = (ChessSquare)((int)piece - 1);
14053         goto defaultlabel;
14054
14055       case DemotePiece:
14056         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14057            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14058             selection = (ChessSquare) (DEMOTED piece);
14059         } else if(piece == EmptySquare) selection = BlackSilver;
14060         else selection = (ChessSquare)((int)piece + 1);
14061         goto defaultlabel;
14062
14063       case WhiteQueen:
14064       case BlackQueen:
14065         if(gameInfo.variant == VariantShatranj ||
14066            gameInfo.variant == VariantXiangqi  ||
14067            gameInfo.variant == VariantCourier  ||
14068            gameInfo.variant == VariantMakruk     )
14069             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14070         goto defaultlabel;
14071
14072       case WhiteKing:
14073       case BlackKing:
14074         if(gameInfo.variant == VariantXiangqi)
14075             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14076         if(gameInfo.variant == VariantKnightmate)
14077             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14078       default:
14079         defaultlabel:
14080         if (gameMode == IcsExamining) {
14081             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14082             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14083                      PieceToChar(selection), AAA + x, ONE + y);
14084             SendToICS(buf);
14085         } else {
14086             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14087                 int n;
14088                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14089                     n = PieceToNumber(selection - BlackPawn);
14090                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14091                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14092                     boards[0][BOARD_HEIGHT-1-n][1]++;
14093                 } else
14094                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14095                     n = PieceToNumber(selection);
14096                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14097                     boards[0][n][BOARD_WIDTH-1] = selection;
14098                     boards[0][n][BOARD_WIDTH-2]++;
14099                 }
14100             } else
14101             boards[0][y][x] = selection;
14102             DrawPosition(TRUE, boards[0]);
14103             ClearHighlights();
14104             fromX = fromY = -1;
14105         }
14106         break;
14107     }
14108 }
14109
14110
14111 void
14112 DropMenuEvent (ChessSquare selection, int x, int y)
14113 {
14114     ChessMove moveType;
14115
14116     switch (gameMode) {
14117       case IcsPlayingWhite:
14118       case MachinePlaysBlack:
14119         if (!WhiteOnMove(currentMove)) {
14120             DisplayMoveError(_("It is Black's turn"));
14121             return;
14122         }
14123         moveType = WhiteDrop;
14124         break;
14125       case IcsPlayingBlack:
14126       case MachinePlaysWhite:
14127         if (WhiteOnMove(currentMove)) {
14128             DisplayMoveError(_("It is White's turn"));
14129             return;
14130         }
14131         moveType = BlackDrop;
14132         break;
14133       case EditGame:
14134         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14135         break;
14136       default:
14137         return;
14138     }
14139
14140     if (moveType == BlackDrop && selection < BlackPawn) {
14141       selection = (ChessSquare) ((int) selection
14142                                  + (int) BlackPawn - (int) WhitePawn);
14143     }
14144     if (boards[currentMove][y][x] != EmptySquare) {
14145         DisplayMoveError(_("That square is occupied"));
14146         return;
14147     }
14148
14149     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14150 }
14151
14152 void
14153 AcceptEvent ()
14154 {
14155     /* Accept a pending offer of any kind from opponent */
14156
14157     if (appData.icsActive) {
14158         SendToICS(ics_prefix);
14159         SendToICS("accept\n");
14160     } else if (cmailMsgLoaded) {
14161         if (currentMove == cmailOldMove &&
14162             commentList[cmailOldMove] != NULL &&
14163             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14164                    "Black offers a draw" : "White offers a draw")) {
14165             TruncateGame();
14166             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14167             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14168         } else {
14169             DisplayError(_("There is no pending offer on this move"), 0);
14170             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14171         }
14172     } else {
14173         /* Not used for offers from chess program */
14174     }
14175 }
14176
14177 void
14178 DeclineEvent ()
14179 {
14180     /* Decline a pending offer of any kind from opponent */
14181
14182     if (appData.icsActive) {
14183         SendToICS(ics_prefix);
14184         SendToICS("decline\n");
14185     } else if (cmailMsgLoaded) {
14186         if (currentMove == cmailOldMove &&
14187             commentList[cmailOldMove] != NULL &&
14188             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14189                    "Black offers a draw" : "White offers a draw")) {
14190 #ifdef NOTDEF
14191             AppendComment(cmailOldMove, "Draw declined", TRUE);
14192             DisplayComment(cmailOldMove - 1, "Draw declined");
14193 #endif /*NOTDEF*/
14194         } else {
14195             DisplayError(_("There is no pending offer on this move"), 0);
14196         }
14197     } else {
14198         /* Not used for offers from chess program */
14199     }
14200 }
14201
14202 void
14203 RematchEvent ()
14204 {
14205     /* Issue ICS rematch command */
14206     if (appData.icsActive) {
14207         SendToICS(ics_prefix);
14208         SendToICS("rematch\n");
14209     }
14210 }
14211
14212 void
14213 CallFlagEvent ()
14214 {
14215     /* Call your opponent's flag (claim a win on time) */
14216     if (appData.icsActive) {
14217         SendToICS(ics_prefix);
14218         SendToICS("flag\n");
14219     } else {
14220         switch (gameMode) {
14221           default:
14222             return;
14223           case MachinePlaysWhite:
14224             if (whiteFlag) {
14225                 if (blackFlag)
14226                   GameEnds(GameIsDrawn, "Both players ran out of time",
14227                            GE_PLAYER);
14228                 else
14229                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14230             } else {
14231                 DisplayError(_("Your opponent is not out of time"), 0);
14232             }
14233             break;
14234           case MachinePlaysBlack:
14235             if (blackFlag) {
14236                 if (whiteFlag)
14237                   GameEnds(GameIsDrawn, "Both players ran out of time",
14238                            GE_PLAYER);
14239                 else
14240                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14241             } else {
14242                 DisplayError(_("Your opponent is not out of time"), 0);
14243             }
14244             break;
14245         }
14246     }
14247 }
14248
14249 void
14250 ClockClick (int which)
14251 {       // [HGM] code moved to back-end from winboard.c
14252         if(which) { // black clock
14253           if (gameMode == EditPosition || gameMode == IcsExamining) {
14254             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14255             SetBlackToPlayEvent();
14256           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14257           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14258           } else if (shiftKey) {
14259             AdjustClock(which, -1);
14260           } else if (gameMode == IcsPlayingWhite ||
14261                      gameMode == MachinePlaysBlack) {
14262             CallFlagEvent();
14263           }
14264         } else { // white clock
14265           if (gameMode == EditPosition || gameMode == IcsExamining) {
14266             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14267             SetWhiteToPlayEvent();
14268           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14269           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14270           } else if (shiftKey) {
14271             AdjustClock(which, -1);
14272           } else if (gameMode == IcsPlayingBlack ||
14273                    gameMode == MachinePlaysWhite) {
14274             CallFlagEvent();
14275           }
14276         }
14277 }
14278
14279 void
14280 DrawEvent ()
14281 {
14282     /* Offer draw or accept pending draw offer from opponent */
14283
14284     if (appData.icsActive) {
14285         /* Note: tournament rules require draw offers to be
14286            made after you make your move but before you punch
14287            your clock.  Currently ICS doesn't let you do that;
14288            instead, you immediately punch your clock after making
14289            a move, but you can offer a draw at any time. */
14290
14291         SendToICS(ics_prefix);
14292         SendToICS("draw\n");
14293         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14294     } else if (cmailMsgLoaded) {
14295         if (currentMove == cmailOldMove &&
14296             commentList[cmailOldMove] != NULL &&
14297             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14298                    "Black offers a draw" : "White offers a draw")) {
14299             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14300             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14301         } else if (currentMove == cmailOldMove + 1) {
14302             char *offer = WhiteOnMove(cmailOldMove) ?
14303               "White offers a draw" : "Black offers a draw";
14304             AppendComment(currentMove, offer, TRUE);
14305             DisplayComment(currentMove - 1, offer);
14306             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14307         } else {
14308             DisplayError(_("You must make your move before offering a draw"), 0);
14309             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14310         }
14311     } else if (first.offeredDraw) {
14312         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14313     } else {
14314         if (first.sendDrawOffers) {
14315             SendToProgram("draw\n", &first);
14316             userOfferedDraw = TRUE;
14317         }
14318     }
14319 }
14320
14321 void
14322 AdjournEvent ()
14323 {
14324     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14325
14326     if (appData.icsActive) {
14327         SendToICS(ics_prefix);
14328         SendToICS("adjourn\n");
14329     } else {
14330         /* Currently GNU Chess doesn't offer or accept Adjourns */
14331     }
14332 }
14333
14334
14335 void
14336 AbortEvent ()
14337 {
14338     /* Offer Abort or accept pending Abort offer from opponent */
14339
14340     if (appData.icsActive) {
14341         SendToICS(ics_prefix);
14342         SendToICS("abort\n");
14343     } else {
14344         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14345     }
14346 }
14347
14348 void
14349 ResignEvent ()
14350 {
14351     /* Resign.  You can do this even if it's not your turn. */
14352
14353     if (appData.icsActive) {
14354         SendToICS(ics_prefix);
14355         SendToICS("resign\n");
14356     } else {
14357         switch (gameMode) {
14358           case MachinePlaysWhite:
14359             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14360             break;
14361           case MachinePlaysBlack:
14362             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14363             break;
14364           case EditGame:
14365             if (cmailMsgLoaded) {
14366                 TruncateGame();
14367                 if (WhiteOnMove(cmailOldMove)) {
14368                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14369                 } else {
14370                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14371                 }
14372                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14373             }
14374             break;
14375           default:
14376             break;
14377         }
14378     }
14379 }
14380
14381
14382 void
14383 StopObservingEvent ()
14384 {
14385     /* Stop observing current games */
14386     SendToICS(ics_prefix);
14387     SendToICS("unobserve\n");
14388 }
14389
14390 void
14391 StopExaminingEvent ()
14392 {
14393     /* Stop observing current game */
14394     SendToICS(ics_prefix);
14395     SendToICS("unexamine\n");
14396 }
14397
14398 void
14399 ForwardInner (int target)
14400 {
14401     int limit; int oldSeekGraphUp = seekGraphUp;
14402
14403     if (appData.debugMode)
14404         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14405                 target, currentMove, forwardMostMove);
14406
14407     if (gameMode == EditPosition)
14408       return;
14409
14410     seekGraphUp = FALSE;
14411     MarkTargetSquares(1);
14412
14413     if (gameMode == PlayFromGameFile && !pausing)
14414       PauseEvent();
14415
14416     if (gameMode == IcsExamining && pausing)
14417       limit = pauseExamForwardMostMove;
14418     else
14419       limit = forwardMostMove;
14420
14421     if (target > limit) target = limit;
14422
14423     if (target > 0 && moveList[target - 1][0]) {
14424         int fromX, fromY, toX, toY;
14425         toX = moveList[target - 1][2] - AAA;
14426         toY = moveList[target - 1][3] - ONE;
14427         if (moveList[target - 1][1] == '@') {
14428             if (appData.highlightLastMove) {
14429                 SetHighlights(-1, -1, toX, toY);
14430             }
14431         } else {
14432             fromX = moveList[target - 1][0] - AAA;
14433             fromY = moveList[target - 1][1] - ONE;
14434             if (target == currentMove + 1) {
14435                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14436             }
14437             if (appData.highlightLastMove) {
14438                 SetHighlights(fromX, fromY, toX, toY);
14439             }
14440         }
14441     }
14442     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14443         gameMode == Training || gameMode == PlayFromGameFile ||
14444         gameMode == AnalyzeFile) {
14445         while (currentMove < target) {
14446             SendMoveToProgram(currentMove++, &first);
14447         }
14448     } else {
14449         currentMove = target;
14450     }
14451
14452     if (gameMode == EditGame || gameMode == EndOfGame) {
14453         whiteTimeRemaining = timeRemaining[0][currentMove];
14454         blackTimeRemaining = timeRemaining[1][currentMove];
14455     }
14456     DisplayBothClocks();
14457     DisplayMove(currentMove - 1);
14458     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14459     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14460     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14461         DisplayComment(currentMove - 1, commentList[currentMove]);
14462     }
14463     ClearMap(); // [HGM] exclude: invalidate map
14464 }
14465
14466
14467 void
14468 ForwardEvent ()
14469 {
14470     if (gameMode == IcsExamining && !pausing) {
14471         SendToICS(ics_prefix);
14472         SendToICS("forward\n");
14473     } else {
14474         ForwardInner(currentMove + 1);
14475     }
14476 }
14477
14478 void
14479 ToEndEvent ()
14480 {
14481     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14482         /* to optimze, we temporarily turn off analysis mode while we feed
14483          * the remaining moves to the engine. Otherwise we get analysis output
14484          * after each move.
14485          */
14486         if (first.analysisSupport) {
14487           SendToProgram("exit\nforce\n", &first);
14488           first.analyzing = FALSE;
14489         }
14490     }
14491
14492     if (gameMode == IcsExamining && !pausing) {
14493         SendToICS(ics_prefix);
14494         SendToICS("forward 999999\n");
14495     } else {
14496         ForwardInner(forwardMostMove);
14497     }
14498
14499     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14500         /* we have fed all the moves, so reactivate analysis mode */
14501         SendToProgram("analyze\n", &first);
14502         first.analyzing = TRUE;
14503         /*first.maybeThinking = TRUE;*/
14504         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14505     }
14506 }
14507
14508 void
14509 BackwardInner (int target)
14510 {
14511     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14512
14513     if (appData.debugMode)
14514         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14515                 target, currentMove, forwardMostMove);
14516
14517     if (gameMode == EditPosition) return;
14518     seekGraphUp = FALSE;
14519     MarkTargetSquares(1);
14520     if (currentMove <= backwardMostMove) {
14521         ClearHighlights();
14522         DrawPosition(full_redraw, boards[currentMove]);
14523         return;
14524     }
14525     if (gameMode == PlayFromGameFile && !pausing)
14526       PauseEvent();
14527
14528     if (moveList[target][0]) {
14529         int fromX, fromY, toX, toY;
14530         toX = moveList[target][2] - AAA;
14531         toY = moveList[target][3] - ONE;
14532         if (moveList[target][1] == '@') {
14533             if (appData.highlightLastMove) {
14534                 SetHighlights(-1, -1, toX, toY);
14535             }
14536         } else {
14537             fromX = moveList[target][0] - AAA;
14538             fromY = moveList[target][1] - ONE;
14539             if (target == currentMove - 1) {
14540                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14541             }
14542             if (appData.highlightLastMove) {
14543                 SetHighlights(fromX, fromY, toX, toY);
14544             }
14545         }
14546     }
14547     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14548         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14549         while (currentMove > target) {
14550             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14551                 // null move cannot be undone. Reload program with move history before it.
14552                 int i;
14553                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14554                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14555                 }
14556                 SendBoard(&first, i); 
14557                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14558                 break;
14559             }
14560             SendToProgram("undo\n", &first);
14561             currentMove--;
14562         }
14563     } else {
14564         currentMove = target;
14565     }
14566
14567     if (gameMode == EditGame || gameMode == EndOfGame) {
14568         whiteTimeRemaining = timeRemaining[0][currentMove];
14569         blackTimeRemaining = timeRemaining[1][currentMove];
14570     }
14571     DisplayBothClocks();
14572     DisplayMove(currentMove - 1);
14573     DrawPosition(full_redraw, boards[currentMove]);
14574     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14575     // [HGM] PV info: routine tests if comment empty
14576     DisplayComment(currentMove - 1, commentList[currentMove]);
14577     ClearMap(); // [HGM] exclude: invalidate map
14578 }
14579
14580 void
14581 BackwardEvent ()
14582 {
14583     if (gameMode == IcsExamining && !pausing) {
14584         SendToICS(ics_prefix);
14585         SendToICS("backward\n");
14586     } else {
14587         BackwardInner(currentMove - 1);
14588     }
14589 }
14590
14591 void
14592 ToStartEvent ()
14593 {
14594     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14595         /* to optimize, we temporarily turn off analysis mode while we undo
14596          * all the moves. Otherwise we get analysis output after each undo.
14597          */
14598         if (first.analysisSupport) {
14599           SendToProgram("exit\nforce\n", &first);
14600           first.analyzing = FALSE;
14601         }
14602     }
14603
14604     if (gameMode == IcsExamining && !pausing) {
14605         SendToICS(ics_prefix);
14606         SendToICS("backward 999999\n");
14607     } else {
14608         BackwardInner(backwardMostMove);
14609     }
14610
14611     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14612         /* we have fed all the moves, so reactivate analysis mode */
14613         SendToProgram("analyze\n", &first);
14614         first.analyzing = TRUE;
14615         /*first.maybeThinking = TRUE;*/
14616         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14617     }
14618 }
14619
14620 void
14621 ToNrEvent (int to)
14622 {
14623   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14624   if (to >= forwardMostMove) to = forwardMostMove;
14625   if (to <= backwardMostMove) to = backwardMostMove;
14626   if (to < currentMove) {
14627     BackwardInner(to);
14628   } else {
14629     ForwardInner(to);
14630   }
14631 }
14632
14633 void
14634 RevertEvent (Boolean annotate)
14635 {
14636     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14637         return;
14638     }
14639     if (gameMode != IcsExamining) {
14640         DisplayError(_("You are not examining a game"), 0);
14641         return;
14642     }
14643     if (pausing) {
14644         DisplayError(_("You can't revert while pausing"), 0);
14645         return;
14646     }
14647     SendToICS(ics_prefix);
14648     SendToICS("revert\n");
14649 }
14650
14651 void
14652 RetractMoveEvent ()
14653 {
14654     switch (gameMode) {
14655       case MachinePlaysWhite:
14656       case MachinePlaysBlack:
14657         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14658             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14659             return;
14660         }
14661         if (forwardMostMove < 2) return;
14662         currentMove = forwardMostMove = forwardMostMove - 2;
14663         whiteTimeRemaining = timeRemaining[0][currentMove];
14664         blackTimeRemaining = timeRemaining[1][currentMove];
14665         DisplayBothClocks();
14666         DisplayMove(currentMove - 1);
14667         ClearHighlights();/*!! could figure this out*/
14668         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14669         SendToProgram("remove\n", &first);
14670         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14671         break;
14672
14673       case BeginningOfGame:
14674       default:
14675         break;
14676
14677       case IcsPlayingWhite:
14678       case IcsPlayingBlack:
14679         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14680             SendToICS(ics_prefix);
14681             SendToICS("takeback 2\n");
14682         } else {
14683             SendToICS(ics_prefix);
14684             SendToICS("takeback 1\n");
14685         }
14686         break;
14687     }
14688 }
14689
14690 void
14691 MoveNowEvent ()
14692 {
14693     ChessProgramState *cps;
14694
14695     switch (gameMode) {
14696       case MachinePlaysWhite:
14697         if (!WhiteOnMove(forwardMostMove)) {
14698             DisplayError(_("It is your turn"), 0);
14699             return;
14700         }
14701         cps = &first;
14702         break;
14703       case MachinePlaysBlack:
14704         if (WhiteOnMove(forwardMostMove)) {
14705             DisplayError(_("It is your turn"), 0);
14706             return;
14707         }
14708         cps = &first;
14709         break;
14710       case TwoMachinesPlay:
14711         if (WhiteOnMove(forwardMostMove) ==
14712             (first.twoMachinesColor[0] == 'w')) {
14713             cps = &first;
14714         } else {
14715             cps = &second;
14716         }
14717         break;
14718       case BeginningOfGame:
14719       default:
14720         return;
14721     }
14722     SendToProgram("?\n", cps);
14723 }
14724
14725 void
14726 TruncateGameEvent ()
14727 {
14728     EditGameEvent();
14729     if (gameMode != EditGame) return;
14730     TruncateGame();
14731 }
14732
14733 void
14734 TruncateGame ()
14735 {
14736     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14737     if (forwardMostMove > currentMove) {
14738         if (gameInfo.resultDetails != NULL) {
14739             free(gameInfo.resultDetails);
14740             gameInfo.resultDetails = NULL;
14741             gameInfo.result = GameUnfinished;
14742         }
14743         forwardMostMove = currentMove;
14744         HistorySet(parseList, backwardMostMove, forwardMostMove,
14745                    currentMove-1);
14746     }
14747 }
14748
14749 void
14750 HintEvent ()
14751 {
14752     if (appData.noChessProgram) return;
14753     switch (gameMode) {
14754       case MachinePlaysWhite:
14755         if (WhiteOnMove(forwardMostMove)) {
14756             DisplayError(_("Wait until your turn"), 0);
14757             return;
14758         }
14759         break;
14760       case BeginningOfGame:
14761       case MachinePlaysBlack:
14762         if (!WhiteOnMove(forwardMostMove)) {
14763             DisplayError(_("Wait until your turn"), 0);
14764             return;
14765         }
14766         break;
14767       default:
14768         DisplayError(_("No hint available"), 0);
14769         return;
14770     }
14771     SendToProgram("hint\n", &first);
14772     hintRequested = TRUE;
14773 }
14774
14775 void
14776 BookEvent ()
14777 {
14778     if (appData.noChessProgram) return;
14779     switch (gameMode) {
14780       case MachinePlaysWhite:
14781         if (WhiteOnMove(forwardMostMove)) {
14782             DisplayError(_("Wait until your turn"), 0);
14783             return;
14784         }
14785         break;
14786       case BeginningOfGame:
14787       case MachinePlaysBlack:
14788         if (!WhiteOnMove(forwardMostMove)) {
14789             DisplayError(_("Wait until your turn"), 0);
14790             return;
14791         }
14792         break;
14793       case EditPosition:
14794         EditPositionDone(TRUE);
14795         break;
14796       case TwoMachinesPlay:
14797         return;
14798       default:
14799         break;
14800     }
14801     SendToProgram("bk\n", &first);
14802     bookOutput[0] = NULLCHAR;
14803     bookRequested = TRUE;
14804 }
14805
14806 void
14807 AboutGameEvent ()
14808 {
14809     char *tags = PGNTags(&gameInfo);
14810     TagsPopUp(tags, CmailMsg());
14811     free(tags);
14812 }
14813
14814 /* end button procedures */
14815
14816 void
14817 PrintPosition (FILE *fp, int move)
14818 {
14819     int i, j;
14820
14821     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14822         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14823             char c = PieceToChar(boards[move][i][j]);
14824             fputc(c == 'x' ? '.' : c, fp);
14825             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14826         }
14827     }
14828     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14829       fprintf(fp, "white to play\n");
14830     else
14831       fprintf(fp, "black to play\n");
14832 }
14833
14834 void
14835 PrintOpponents (FILE *fp)
14836 {
14837     if (gameInfo.white != NULL) {
14838         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14839     } else {
14840         fprintf(fp, "\n");
14841     }
14842 }
14843
14844 /* Find last component of program's own name, using some heuristics */
14845 void
14846 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14847 {
14848     char *p, *q, c;
14849     int local = (strcmp(host, "localhost") == 0);
14850     while (!local && (p = strchr(prog, ';')) != NULL) {
14851         p++;
14852         while (*p == ' ') p++;
14853         prog = p;
14854     }
14855     if (*prog == '"' || *prog == '\'') {
14856         q = strchr(prog + 1, *prog);
14857     } else {
14858         q = strchr(prog, ' ');
14859     }
14860     if (q == NULL) q = prog + strlen(prog);
14861     p = q;
14862     while (p >= prog && *p != '/' && *p != '\\') p--;
14863     p++;
14864     if(p == prog && *p == '"') p++;
14865     c = *q; *q = 0;
14866     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14867     memcpy(buf, p, q - p);
14868     buf[q - p] = NULLCHAR;
14869     if (!local) {
14870         strcat(buf, "@");
14871         strcat(buf, host);
14872     }
14873 }
14874
14875 char *
14876 TimeControlTagValue ()
14877 {
14878     char buf[MSG_SIZ];
14879     if (!appData.clockMode) {
14880       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14881     } else if (movesPerSession > 0) {
14882       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14883     } else if (timeIncrement == 0) {
14884       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14885     } else {
14886       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14887     }
14888     return StrSave(buf);
14889 }
14890
14891 void
14892 SetGameInfo ()
14893 {
14894     /* This routine is used only for certain modes */
14895     VariantClass v = gameInfo.variant;
14896     ChessMove r = GameUnfinished;
14897     char *p = NULL;
14898
14899     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14900         r = gameInfo.result;
14901         p = gameInfo.resultDetails;
14902         gameInfo.resultDetails = NULL;
14903     }
14904     ClearGameInfo(&gameInfo);
14905     gameInfo.variant = v;
14906
14907     switch (gameMode) {
14908       case MachinePlaysWhite:
14909         gameInfo.event = StrSave( appData.pgnEventHeader );
14910         gameInfo.site = StrSave(HostName());
14911         gameInfo.date = PGNDate();
14912         gameInfo.round = StrSave("-");
14913         gameInfo.white = StrSave(first.tidy);
14914         gameInfo.black = StrSave(UserName());
14915         gameInfo.timeControl = TimeControlTagValue();
14916         break;
14917
14918       case MachinePlaysBlack:
14919         gameInfo.event = StrSave( appData.pgnEventHeader );
14920         gameInfo.site = StrSave(HostName());
14921         gameInfo.date = PGNDate();
14922         gameInfo.round = StrSave("-");
14923         gameInfo.white = StrSave(UserName());
14924         gameInfo.black = StrSave(first.tidy);
14925         gameInfo.timeControl = TimeControlTagValue();
14926         break;
14927
14928       case TwoMachinesPlay:
14929         gameInfo.event = StrSave( appData.pgnEventHeader );
14930         gameInfo.site = StrSave(HostName());
14931         gameInfo.date = PGNDate();
14932         if (roundNr > 0) {
14933             char buf[MSG_SIZ];
14934             snprintf(buf, MSG_SIZ, "%d", roundNr);
14935             gameInfo.round = StrSave(buf);
14936         } else {
14937             gameInfo.round = StrSave("-");
14938         }
14939         if (first.twoMachinesColor[0] == 'w') {
14940             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14941             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14942         } else {
14943             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14944             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14945         }
14946         gameInfo.timeControl = TimeControlTagValue();
14947         break;
14948
14949       case EditGame:
14950         gameInfo.event = StrSave("Edited game");
14951         gameInfo.site = StrSave(HostName());
14952         gameInfo.date = PGNDate();
14953         gameInfo.round = StrSave("-");
14954         gameInfo.white = StrSave("-");
14955         gameInfo.black = StrSave("-");
14956         gameInfo.result = r;
14957         gameInfo.resultDetails = p;
14958         break;
14959
14960       case EditPosition:
14961         gameInfo.event = StrSave("Edited position");
14962         gameInfo.site = StrSave(HostName());
14963         gameInfo.date = PGNDate();
14964         gameInfo.round = StrSave("-");
14965         gameInfo.white = StrSave("-");
14966         gameInfo.black = StrSave("-");
14967         break;
14968
14969       case IcsPlayingWhite:
14970       case IcsPlayingBlack:
14971       case IcsObserving:
14972       case IcsExamining:
14973         break;
14974
14975       case PlayFromGameFile:
14976         gameInfo.event = StrSave("Game from non-PGN file");
14977         gameInfo.site = StrSave(HostName());
14978         gameInfo.date = PGNDate();
14979         gameInfo.round = StrSave("-");
14980         gameInfo.white = StrSave("?");
14981         gameInfo.black = StrSave("?");
14982         break;
14983
14984       default:
14985         break;
14986     }
14987 }
14988
14989 void
14990 ReplaceComment (int index, char *text)
14991 {
14992     int len;
14993     char *p;
14994     float score;
14995
14996     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14997        pvInfoList[index-1].depth == len &&
14998        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14999        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15000     while (*text == '\n') text++;
15001     len = strlen(text);
15002     while (len > 0 && text[len - 1] == '\n') len--;
15003
15004     if (commentList[index] != NULL)
15005       free(commentList[index]);
15006
15007     if (len == 0) {
15008         commentList[index] = NULL;
15009         return;
15010     }
15011   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15012       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15013       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15014     commentList[index] = (char *) malloc(len + 2);
15015     strncpy(commentList[index], text, len);
15016     commentList[index][len] = '\n';
15017     commentList[index][len + 1] = NULLCHAR;
15018   } else {
15019     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15020     char *p;
15021     commentList[index] = (char *) malloc(len + 7);
15022     safeStrCpy(commentList[index], "{\n", 3);
15023     safeStrCpy(commentList[index]+2, text, len+1);
15024     commentList[index][len+2] = NULLCHAR;
15025     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15026     strcat(commentList[index], "\n}\n");
15027   }
15028 }
15029
15030 void
15031 CrushCRs (char *text)
15032 {
15033   char *p = text;
15034   char *q = text;
15035   char ch;
15036
15037   do {
15038     ch = *p++;
15039     if (ch == '\r') continue;
15040     *q++ = ch;
15041   } while (ch != '\0');
15042 }
15043
15044 void
15045 AppendComment (int index, char *text, Boolean addBraces)
15046 /* addBraces  tells if we should add {} */
15047 {
15048     int oldlen, len;
15049     char *old;
15050
15051 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15052     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15053
15054     CrushCRs(text);
15055     while (*text == '\n') text++;
15056     len = strlen(text);
15057     while (len > 0 && text[len - 1] == '\n') len--;
15058     text[len] = NULLCHAR;
15059
15060     if (len == 0) return;
15061
15062     if (commentList[index] != NULL) {
15063       Boolean addClosingBrace = addBraces;
15064         old = commentList[index];
15065         oldlen = strlen(old);
15066         while(commentList[index][oldlen-1] ==  '\n')
15067           commentList[index][--oldlen] = NULLCHAR;
15068         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15069         safeStrCpy(commentList[index], old, oldlen + len + 6);
15070         free(old);
15071         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15072         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15073           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15074           while (*text == '\n') { text++; len--; }
15075           commentList[index][--oldlen] = NULLCHAR;
15076       }
15077         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15078         else          strcat(commentList[index], "\n");
15079         strcat(commentList[index], text);
15080         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15081         else          strcat(commentList[index], "\n");
15082     } else {
15083         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15084         if(addBraces)
15085           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15086         else commentList[index][0] = NULLCHAR;
15087         strcat(commentList[index], text);
15088         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15089         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15090     }
15091 }
15092
15093 static char *
15094 FindStr (char * text, char * sub_text)
15095 {
15096     char * result = strstr( text, sub_text );
15097
15098     if( result != NULL ) {
15099         result += strlen( sub_text );
15100     }
15101
15102     return result;
15103 }
15104
15105 /* [AS] Try to extract PV info from PGN comment */
15106 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15107 char *
15108 GetInfoFromComment (int index, char * text)
15109 {
15110     char * sep = text, *p;
15111
15112     if( text != NULL && index > 0 ) {
15113         int score = 0;
15114         int depth = 0;
15115         int time = -1, sec = 0, deci;
15116         char * s_eval = FindStr( text, "[%eval " );
15117         char * s_emt = FindStr( text, "[%emt " );
15118
15119         if( s_eval != NULL || s_emt != NULL ) {
15120             /* New style */
15121             char delim;
15122
15123             if( s_eval != NULL ) {
15124                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15125                     return text;
15126                 }
15127
15128                 if( delim != ']' ) {
15129                     return text;
15130                 }
15131             }
15132
15133             if( s_emt != NULL ) {
15134             }
15135                 return text;
15136         }
15137         else {
15138             /* We expect something like: [+|-]nnn.nn/dd */
15139             int score_lo = 0;
15140
15141             if(*text != '{') return text; // [HGM] braces: must be normal comment
15142
15143             sep = strchr( text, '/' );
15144             if( sep == NULL || sep < (text+4) ) {
15145                 return text;
15146             }
15147
15148             p = text;
15149             if(p[1] == '(') { // comment starts with PV
15150                p = strchr(p, ')'); // locate end of PV
15151                if(p == NULL || sep < p+5) return text;
15152                // at this point we have something like "{(.*) +0.23/6 ..."
15153                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15154                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15155                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15156             }
15157             time = -1; sec = -1; deci = -1;
15158             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15159                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15160                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15161                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15162                 return text;
15163             }
15164
15165             if( score_lo < 0 || score_lo >= 100 ) {
15166                 return text;
15167             }
15168
15169             if(sec >= 0) time = 600*time + 10*sec; else
15170             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15171
15172             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15173
15174             /* [HGM] PV time: now locate end of PV info */
15175             while( *++sep >= '0' && *sep <= '9'); // strip depth
15176             if(time >= 0)
15177             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15178             if(sec >= 0)
15179             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15180             if(deci >= 0)
15181             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15182             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15183         }
15184
15185         if( depth <= 0 ) {
15186             return text;
15187         }
15188
15189         if( time < 0 ) {
15190             time = -1;
15191         }
15192
15193         pvInfoList[index-1].depth = depth;
15194         pvInfoList[index-1].score = score;
15195         pvInfoList[index-1].time  = 10*time; // centi-sec
15196         if(*sep == '}') *sep = 0; else *--sep = '{';
15197         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15198     }
15199     return sep;
15200 }
15201
15202 void
15203 SendToProgram (char *message, ChessProgramState *cps)
15204 {
15205     int count, outCount, error;
15206     char buf[MSG_SIZ];
15207
15208     if (cps->pr == NoProc) return;
15209     Attention(cps);
15210
15211     if (appData.debugMode) {
15212         TimeMark now;
15213         GetTimeMark(&now);
15214         fprintf(debugFP, "%ld >%-6s: %s",
15215                 SubtractTimeMarks(&now, &programStartTime),
15216                 cps->which, message);
15217         if(serverFP)
15218             fprintf(serverFP, "%ld >%-6s: %s",
15219                 SubtractTimeMarks(&now, &programStartTime),
15220                 cps->which, message), fflush(serverFP);
15221     }
15222
15223     count = strlen(message);
15224     outCount = OutputToProcess(cps->pr, message, count, &error);
15225     if (outCount < count && !exiting
15226                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15227       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15228       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15229         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15230             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15231                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15232                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15233                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15234             } else {
15235                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15236                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15237                 gameInfo.result = res;
15238             }
15239             gameInfo.resultDetails = StrSave(buf);
15240         }
15241         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15242         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15243     }
15244 }
15245
15246 void
15247 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15248 {
15249     char *end_str;
15250     char buf[MSG_SIZ];
15251     ChessProgramState *cps = (ChessProgramState *)closure;
15252
15253     if (isr != cps->isr) return; /* Killed intentionally */
15254     if (count <= 0) {
15255         if (count == 0) {
15256             RemoveInputSource(cps->isr);
15257             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15258             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15259                     _(cps->which), cps->program);
15260         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15261                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15262                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15263                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15264                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15265                 } else {
15266                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15267                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15268                     gameInfo.result = res;
15269                 }
15270                 gameInfo.resultDetails = StrSave(buf);
15271             }
15272             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15273             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15274         } else {
15275             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15276                     _(cps->which), cps->program);
15277             RemoveInputSource(cps->isr);
15278
15279             /* [AS] Program is misbehaving badly... kill it */
15280             if( count == -2 ) {
15281                 DestroyChildProcess( cps->pr, 9 );
15282                 cps->pr = NoProc;
15283             }
15284
15285             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15286         }
15287         return;
15288     }
15289
15290     if ((end_str = strchr(message, '\r')) != NULL)
15291       *end_str = NULLCHAR;
15292     if ((end_str = strchr(message, '\n')) != NULL)
15293       *end_str = NULLCHAR;
15294
15295     if (appData.debugMode) {
15296         TimeMark now; int print = 1;
15297         char *quote = ""; char c; int i;
15298
15299         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15300                 char start = message[0];
15301                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15302                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15303                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15304                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15305                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15306                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15307                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15308                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15309                    sscanf(message, "hint: %c", &c)!=1 && 
15310                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15311                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15312                     print = (appData.engineComments >= 2);
15313                 }
15314                 message[0] = start; // restore original message
15315         }
15316         if(print) {
15317                 GetTimeMark(&now);
15318                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15319                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15320                         quote,
15321                         message);
15322                 if(serverFP)
15323                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15324                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15325                         quote,
15326                         message), fflush(serverFP);
15327         }
15328     }
15329
15330     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15331     if (appData.icsEngineAnalyze) {
15332         if (strstr(message, "whisper") != NULL ||
15333              strstr(message, "kibitz") != NULL ||
15334             strstr(message, "tellics") != NULL) return;
15335     }
15336
15337     HandleMachineMove(message, cps);
15338 }
15339
15340
15341 void
15342 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15343 {
15344     char buf[MSG_SIZ];
15345     int seconds;
15346
15347     if( timeControl_2 > 0 ) {
15348         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15349             tc = timeControl_2;
15350         }
15351     }
15352     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15353     inc /= cps->timeOdds;
15354     st  /= cps->timeOdds;
15355
15356     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15357
15358     if (st > 0) {
15359       /* Set exact time per move, normally using st command */
15360       if (cps->stKludge) {
15361         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15362         seconds = st % 60;
15363         if (seconds == 0) {
15364           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15365         } else {
15366           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15367         }
15368       } else {
15369         snprintf(buf, MSG_SIZ, "st %d\n", st);
15370       }
15371     } else {
15372       /* Set conventional or incremental time control, using level command */
15373       if (seconds == 0) {
15374         /* Note old gnuchess bug -- minutes:seconds used to not work.
15375            Fixed in later versions, but still avoid :seconds
15376            when seconds is 0. */
15377         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15378       } else {
15379         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15380                  seconds, inc/1000.);
15381       }
15382     }
15383     SendToProgram(buf, cps);
15384
15385     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15386     /* Orthogonally, limit search to given depth */
15387     if (sd > 0) {
15388       if (cps->sdKludge) {
15389         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15390       } else {
15391         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15392       }
15393       SendToProgram(buf, cps);
15394     }
15395
15396     if(cps->nps >= 0) { /* [HGM] nps */
15397         if(cps->supportsNPS == FALSE)
15398           cps->nps = -1; // don't use if engine explicitly says not supported!
15399         else {
15400           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15401           SendToProgram(buf, cps);
15402         }
15403     }
15404 }
15405
15406 ChessProgramState *
15407 WhitePlayer ()
15408 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15409 {
15410     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15411        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15412         return &second;
15413     return &first;
15414 }
15415
15416 void
15417 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15418 {
15419     char message[MSG_SIZ];
15420     long time, otime;
15421
15422     /* Note: this routine must be called when the clocks are stopped
15423        or when they have *just* been set or switched; otherwise
15424        it will be off by the time since the current tick started.
15425     */
15426     if (machineWhite) {
15427         time = whiteTimeRemaining / 10;
15428         otime = blackTimeRemaining / 10;
15429     } else {
15430         time = blackTimeRemaining / 10;
15431         otime = whiteTimeRemaining / 10;
15432     }
15433     /* [HGM] translate opponent's time by time-odds factor */
15434     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15435
15436     if (time <= 0) time = 1;
15437     if (otime <= 0) otime = 1;
15438
15439     snprintf(message, MSG_SIZ, "time %ld\n", time);
15440     SendToProgram(message, cps);
15441
15442     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15443     SendToProgram(message, cps);
15444 }
15445
15446 int
15447 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15448 {
15449   char buf[MSG_SIZ];
15450   int len = strlen(name);
15451   int val;
15452
15453   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15454     (*p) += len + 1;
15455     sscanf(*p, "%d", &val);
15456     *loc = (val != 0);
15457     while (**p && **p != ' ')
15458       (*p)++;
15459     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15460     SendToProgram(buf, cps);
15461     return TRUE;
15462   }
15463   return FALSE;
15464 }
15465
15466 int
15467 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15468 {
15469   char buf[MSG_SIZ];
15470   int len = strlen(name);
15471   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15472     (*p) += len + 1;
15473     sscanf(*p, "%d", loc);
15474     while (**p && **p != ' ') (*p)++;
15475     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15476     SendToProgram(buf, cps);
15477     return TRUE;
15478   }
15479   return FALSE;
15480 }
15481
15482 int
15483 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15484 {
15485   char buf[MSG_SIZ];
15486   int len = strlen(name);
15487   if (strncmp((*p), name, len) == 0
15488       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15489     (*p) += len + 2;
15490     sscanf(*p, "%[^\"]", loc);
15491     while (**p && **p != '\"') (*p)++;
15492     if (**p == '\"') (*p)++;
15493     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15494     SendToProgram(buf, cps);
15495     return TRUE;
15496   }
15497   return FALSE;
15498 }
15499
15500 int
15501 ParseOption (Option *opt, ChessProgramState *cps)
15502 // [HGM] options: process the string that defines an engine option, and determine
15503 // name, type, default value, and allowed value range
15504 {
15505         char *p, *q, buf[MSG_SIZ];
15506         int n, min = (-1)<<31, max = 1<<31, def;
15507
15508         if(p = strstr(opt->name, " -spin ")) {
15509             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15510             if(max < min) max = min; // enforce consistency
15511             if(def < min) def = min;
15512             if(def > max) def = max;
15513             opt->value = def;
15514             opt->min = min;
15515             opt->max = max;
15516             opt->type = Spin;
15517         } else if((p = strstr(opt->name, " -slider "))) {
15518             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15519             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15520             if(max < min) max = min; // enforce consistency
15521             if(def < min) def = min;
15522             if(def > max) def = max;
15523             opt->value = def;
15524             opt->min = min;
15525             opt->max = max;
15526             opt->type = Spin; // Slider;
15527         } else if((p = strstr(opt->name, " -string "))) {
15528             opt->textValue = p+9;
15529             opt->type = TextBox;
15530         } else if((p = strstr(opt->name, " -file "))) {
15531             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15532             opt->textValue = p+7;
15533             opt->type = FileName; // FileName;
15534         } else if((p = strstr(opt->name, " -path "))) {
15535             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15536             opt->textValue = p+7;
15537             opt->type = PathName; // PathName;
15538         } else if(p = strstr(opt->name, " -check ")) {
15539             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15540             opt->value = (def != 0);
15541             opt->type = CheckBox;
15542         } else if(p = strstr(opt->name, " -combo ")) {
15543             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15544             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15545             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15546             opt->value = n = 0;
15547             while(q = StrStr(q, " /// ")) {
15548                 n++; *q = 0;    // count choices, and null-terminate each of them
15549                 q += 5;
15550                 if(*q == '*') { // remember default, which is marked with * prefix
15551                     q++;
15552                     opt->value = n;
15553                 }
15554                 cps->comboList[cps->comboCnt++] = q;
15555             }
15556             cps->comboList[cps->comboCnt++] = NULL;
15557             opt->max = n + 1;
15558             opt->type = ComboBox;
15559         } else if(p = strstr(opt->name, " -button")) {
15560             opt->type = Button;
15561         } else if(p = strstr(opt->name, " -save")) {
15562             opt->type = SaveButton;
15563         } else return FALSE;
15564         *p = 0; // terminate option name
15565         // now look if the command-line options define a setting for this engine option.
15566         if(cps->optionSettings && cps->optionSettings[0])
15567             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15568         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15569           snprintf(buf, MSG_SIZ, "option %s", p);
15570                 if(p = strstr(buf, ",")) *p = 0;
15571                 if(q = strchr(buf, '=')) switch(opt->type) {
15572                     case ComboBox:
15573                         for(n=0; n<opt->max; n++)
15574                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15575                         break;
15576                     case TextBox:
15577                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15578                         break;
15579                     case Spin:
15580                     case CheckBox:
15581                         opt->value = atoi(q+1);
15582                     default:
15583                         break;
15584                 }
15585                 strcat(buf, "\n");
15586                 SendToProgram(buf, cps);
15587         }
15588         return TRUE;
15589 }
15590
15591 void
15592 FeatureDone (ChessProgramState *cps, int val)
15593 {
15594   DelayedEventCallback cb = GetDelayedEvent();
15595   if ((cb == InitBackEnd3 && cps == &first) ||
15596       (cb == SettingsMenuIfReady && cps == &second) ||
15597       (cb == LoadEngine) ||
15598       (cb == TwoMachinesEventIfReady)) {
15599     CancelDelayedEvent();
15600     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15601   }
15602   cps->initDone = val;
15603 }
15604
15605 /* Parse feature command from engine */
15606 void
15607 ParseFeatures (char *args, ChessProgramState *cps)
15608 {
15609   char *p = args;
15610   char *q;
15611   int val;
15612   char buf[MSG_SIZ];
15613
15614   for (;;) {
15615     while (*p == ' ') p++;
15616     if (*p == NULLCHAR) return;
15617
15618     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15619     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15620     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15621     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15622     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15623     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15624     if (BoolFeature(&p, "reuse", &val, cps)) {
15625       /* Engine can disable reuse, but can't enable it if user said no */
15626       if (!val) cps->reuse = FALSE;
15627       continue;
15628     }
15629     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15630     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15631       if (gameMode == TwoMachinesPlay) {
15632         DisplayTwoMachinesTitle();
15633       } else {
15634         DisplayTitle("");
15635       }
15636       continue;
15637     }
15638     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15639     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15640     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15641     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15642     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15643     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15644     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15645     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15646     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15647     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15648     if (IntFeature(&p, "done", &val, cps)) {
15649       FeatureDone(cps, val);
15650       continue;
15651     }
15652     /* Added by Tord: */
15653     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15654     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15655     /* End of additions by Tord */
15656
15657     /* [HGM] added features: */
15658     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15659     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15660     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15661     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15662     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15663     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15664     if (StringFeature(&p, "option", buf, cps)) {
15665         FREE(cps->option[cps->nrOptions].name);
15666         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15667         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15668         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15669           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15670             SendToProgram(buf, cps);
15671             continue;
15672         }
15673         if(cps->nrOptions >= MAX_OPTIONS) {
15674             cps->nrOptions--;
15675             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15676             DisplayError(buf, 0);
15677         }
15678         continue;
15679     }
15680     /* End of additions by HGM */
15681
15682     /* unknown feature: complain and skip */
15683     q = p;
15684     while (*q && *q != '=') q++;
15685     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15686     SendToProgram(buf, cps);
15687     p = q;
15688     if (*p == '=') {
15689       p++;
15690       if (*p == '\"') {
15691         p++;
15692         while (*p && *p != '\"') p++;
15693         if (*p == '\"') p++;
15694       } else {
15695         while (*p && *p != ' ') p++;
15696       }
15697     }
15698   }
15699
15700 }
15701
15702 void
15703 PeriodicUpdatesEvent (int newState)
15704 {
15705     if (newState == appData.periodicUpdates)
15706       return;
15707
15708     appData.periodicUpdates=newState;
15709
15710     /* Display type changes, so update it now */
15711 //    DisplayAnalysis();
15712
15713     /* Get the ball rolling again... */
15714     if (newState) {
15715         AnalysisPeriodicEvent(1);
15716         StartAnalysisClock();
15717     }
15718 }
15719
15720 void
15721 PonderNextMoveEvent (int newState)
15722 {
15723     if (newState == appData.ponderNextMove) return;
15724     if (gameMode == EditPosition) EditPositionDone(TRUE);
15725     if (newState) {
15726         SendToProgram("hard\n", &first);
15727         if (gameMode == TwoMachinesPlay) {
15728             SendToProgram("hard\n", &second);
15729         }
15730     } else {
15731         SendToProgram("easy\n", &first);
15732         thinkOutput[0] = NULLCHAR;
15733         if (gameMode == TwoMachinesPlay) {
15734             SendToProgram("easy\n", &second);
15735         }
15736     }
15737     appData.ponderNextMove = newState;
15738 }
15739
15740 void
15741 NewSettingEvent (int option, int *feature, char *command, int value)
15742 {
15743     char buf[MSG_SIZ];
15744
15745     if (gameMode == EditPosition) EditPositionDone(TRUE);
15746     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15747     if(feature == NULL || *feature) SendToProgram(buf, &first);
15748     if (gameMode == TwoMachinesPlay) {
15749         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15750     }
15751 }
15752
15753 void
15754 ShowThinkingEvent ()
15755 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15756 {
15757     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15758     int newState = appData.showThinking
15759         // [HGM] thinking: other features now need thinking output as well
15760         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15761
15762     if (oldState == newState) return;
15763     oldState = newState;
15764     if (gameMode == EditPosition) EditPositionDone(TRUE);
15765     if (oldState) {
15766         SendToProgram("post\n", &first);
15767         if (gameMode == TwoMachinesPlay) {
15768             SendToProgram("post\n", &second);
15769         }
15770     } else {
15771         SendToProgram("nopost\n", &first);
15772         thinkOutput[0] = NULLCHAR;
15773         if (gameMode == TwoMachinesPlay) {
15774             SendToProgram("nopost\n", &second);
15775         }
15776     }
15777 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15778 }
15779
15780 void
15781 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15782 {
15783   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15784   if (pr == NoProc) return;
15785   AskQuestion(title, question, replyPrefix, pr);
15786 }
15787
15788 void
15789 TypeInEvent (char firstChar)
15790 {
15791     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15792         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15793         gameMode == AnalyzeMode || gameMode == EditGame || 
15794         gameMode == EditPosition || gameMode == IcsExamining ||
15795         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15796         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15797                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15798                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15799         gameMode == Training) PopUpMoveDialog(firstChar);
15800 }
15801
15802 void
15803 TypeInDoneEvent (char *move)
15804 {
15805         Board board;
15806         int n, fromX, fromY, toX, toY;
15807         char promoChar;
15808         ChessMove moveType;
15809
15810         // [HGM] FENedit
15811         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15812                 EditPositionPasteFEN(move);
15813                 return;
15814         }
15815         // [HGM] movenum: allow move number to be typed in any mode
15816         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15817           ToNrEvent(2*n-1);
15818           return;
15819         }
15820         // undocumented kludge: allow command-line option to be typed in!
15821         // (potentially fatal, and does not implement the effect of the option.)
15822         // should only be used for options that are values on which future decisions will be made,
15823         // and definitely not on options that would be used during initialization.
15824         if(strstr(move, "!!! -") == move) {
15825             ParseArgsFromString(move+4);
15826             return;
15827         }
15828
15829       if (gameMode != EditGame && currentMove != forwardMostMove && 
15830         gameMode != Training) {
15831         DisplayMoveError(_("Displayed move is not current"));
15832       } else {
15833         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15834           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15835         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15836         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15837           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15838           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15839         } else {
15840           DisplayMoveError(_("Could not parse move"));
15841         }
15842       }
15843 }
15844
15845 void
15846 DisplayMove (int moveNumber)
15847 {
15848     char message[MSG_SIZ];
15849     char res[MSG_SIZ];
15850     char cpThinkOutput[MSG_SIZ];
15851
15852     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15853
15854     if (moveNumber == forwardMostMove - 1 ||
15855         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15856
15857         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15858
15859         if (strchr(cpThinkOutput, '\n')) {
15860             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15861         }
15862     } else {
15863         *cpThinkOutput = NULLCHAR;
15864     }
15865
15866     /* [AS] Hide thinking from human user */
15867     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15868         *cpThinkOutput = NULLCHAR;
15869         if( thinkOutput[0] != NULLCHAR ) {
15870             int i;
15871
15872             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15873                 cpThinkOutput[i] = '.';
15874             }
15875             cpThinkOutput[i] = NULLCHAR;
15876             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15877         }
15878     }
15879
15880     if (moveNumber == forwardMostMove - 1 &&
15881         gameInfo.resultDetails != NULL) {
15882         if (gameInfo.resultDetails[0] == NULLCHAR) {
15883           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15884         } else {
15885           snprintf(res, MSG_SIZ, " {%s} %s",
15886                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15887         }
15888     } else {
15889         res[0] = NULLCHAR;
15890     }
15891
15892     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15893         DisplayMessage(res, cpThinkOutput);
15894     } else {
15895       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15896                 WhiteOnMove(moveNumber) ? " " : ".. ",
15897                 parseList[moveNumber], res);
15898         DisplayMessage(message, cpThinkOutput);
15899     }
15900 }
15901
15902 void
15903 DisplayComment (int moveNumber, char *text)
15904 {
15905     char title[MSG_SIZ];
15906
15907     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15908       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15909     } else {
15910       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15911               WhiteOnMove(moveNumber) ? " " : ".. ",
15912               parseList[moveNumber]);
15913     }
15914     if (text != NULL && (appData.autoDisplayComment || commentUp))
15915         CommentPopUp(title, text);
15916 }
15917
15918 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15919  * might be busy thinking or pondering.  It can be omitted if your
15920  * gnuchess is configured to stop thinking immediately on any user
15921  * input.  However, that gnuchess feature depends on the FIONREAD
15922  * ioctl, which does not work properly on some flavors of Unix.
15923  */
15924 void
15925 Attention (ChessProgramState *cps)
15926 {
15927 #if ATTENTION
15928     if (!cps->useSigint) return;
15929     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15930     switch (gameMode) {
15931       case MachinePlaysWhite:
15932       case MachinePlaysBlack:
15933       case TwoMachinesPlay:
15934       case IcsPlayingWhite:
15935       case IcsPlayingBlack:
15936       case AnalyzeMode:
15937       case AnalyzeFile:
15938         /* Skip if we know it isn't thinking */
15939         if (!cps->maybeThinking) return;
15940         if (appData.debugMode)
15941           fprintf(debugFP, "Interrupting %s\n", cps->which);
15942         InterruptChildProcess(cps->pr);
15943         cps->maybeThinking = FALSE;
15944         break;
15945       default:
15946         break;
15947     }
15948 #endif /*ATTENTION*/
15949 }
15950
15951 int
15952 CheckFlags ()
15953 {
15954     if (whiteTimeRemaining <= 0) {
15955         if (!whiteFlag) {
15956             whiteFlag = TRUE;
15957             if (appData.icsActive) {
15958                 if (appData.autoCallFlag &&
15959                     gameMode == IcsPlayingBlack && !blackFlag) {
15960                   SendToICS(ics_prefix);
15961                   SendToICS("flag\n");
15962                 }
15963             } else {
15964                 if (blackFlag) {
15965                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15966                 } else {
15967                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15968                     if (appData.autoCallFlag) {
15969                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15970                         return TRUE;
15971                     }
15972                 }
15973             }
15974         }
15975     }
15976     if (blackTimeRemaining <= 0) {
15977         if (!blackFlag) {
15978             blackFlag = TRUE;
15979             if (appData.icsActive) {
15980                 if (appData.autoCallFlag &&
15981                     gameMode == IcsPlayingWhite && !whiteFlag) {
15982                   SendToICS(ics_prefix);
15983                   SendToICS("flag\n");
15984                 }
15985             } else {
15986                 if (whiteFlag) {
15987                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15988                 } else {
15989                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15990                     if (appData.autoCallFlag) {
15991                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15992                         return TRUE;
15993                     }
15994                 }
15995             }
15996         }
15997     }
15998     return FALSE;
15999 }
16000
16001 void
16002 CheckTimeControl ()
16003 {
16004     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16005         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16006
16007     /*
16008      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16009      */
16010     if ( !WhiteOnMove(forwardMostMove) ) {
16011         /* White made time control */
16012         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16013         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16014         /* [HGM] time odds: correct new time quota for time odds! */
16015                                             / WhitePlayer()->timeOdds;
16016         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16017     } else {
16018         lastBlack -= blackTimeRemaining;
16019         /* Black made time control */
16020         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16021                                             / WhitePlayer()->other->timeOdds;
16022         lastWhite = whiteTimeRemaining;
16023     }
16024 }
16025
16026 void
16027 DisplayBothClocks ()
16028 {
16029     int wom = gameMode == EditPosition ?
16030       !blackPlaysFirst : WhiteOnMove(currentMove);
16031     DisplayWhiteClock(whiteTimeRemaining, wom);
16032     DisplayBlackClock(blackTimeRemaining, !wom);
16033 }
16034
16035
16036 /* Timekeeping seems to be a portability nightmare.  I think everyone
16037    has ftime(), but I'm really not sure, so I'm including some ifdefs
16038    to use other calls if you don't.  Clocks will be less accurate if
16039    you have neither ftime nor gettimeofday.
16040 */
16041
16042 /* VS 2008 requires the #include outside of the function */
16043 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16044 #include <sys/timeb.h>
16045 #endif
16046
16047 /* Get the current time as a TimeMark */
16048 void
16049 GetTimeMark (TimeMark *tm)
16050 {
16051 #if HAVE_GETTIMEOFDAY
16052
16053     struct timeval timeVal;
16054     struct timezone timeZone;
16055
16056     gettimeofday(&timeVal, &timeZone);
16057     tm->sec = (long) timeVal.tv_sec;
16058     tm->ms = (int) (timeVal.tv_usec / 1000L);
16059
16060 #else /*!HAVE_GETTIMEOFDAY*/
16061 #if HAVE_FTIME
16062
16063 // include <sys/timeb.h> / moved to just above start of function
16064     struct timeb timeB;
16065
16066     ftime(&timeB);
16067     tm->sec = (long) timeB.time;
16068     tm->ms = (int) timeB.millitm;
16069
16070 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16071     tm->sec = (long) time(NULL);
16072     tm->ms = 0;
16073 #endif
16074 #endif
16075 }
16076
16077 /* Return the difference in milliseconds between two
16078    time marks.  We assume the difference will fit in a long!
16079 */
16080 long
16081 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16082 {
16083     return 1000L*(tm2->sec - tm1->sec) +
16084            (long) (tm2->ms - tm1->ms);
16085 }
16086
16087
16088 /*
16089  * Code to manage the game clocks.
16090  *
16091  * In tournament play, black starts the clock and then white makes a move.
16092  * We give the human user a slight advantage if he is playing white---the
16093  * clocks don't run until he makes his first move, so it takes zero time.
16094  * Also, we don't account for network lag, so we could get out of sync
16095  * with GNU Chess's clock -- but then, referees are always right.
16096  */
16097
16098 static TimeMark tickStartTM;
16099 static long intendedTickLength;
16100
16101 long
16102 NextTickLength (long timeRemaining)
16103 {
16104     long nominalTickLength, nextTickLength;
16105
16106     if (timeRemaining > 0L && timeRemaining <= 10000L)
16107       nominalTickLength = 100L;
16108     else
16109       nominalTickLength = 1000L;
16110     nextTickLength = timeRemaining % nominalTickLength;
16111     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16112
16113     return nextTickLength;
16114 }
16115
16116 /* Adjust clock one minute up or down */
16117 void
16118 AdjustClock (Boolean which, int dir)
16119 {
16120     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16121     if(which) blackTimeRemaining += 60000*dir;
16122     else      whiteTimeRemaining += 60000*dir;
16123     DisplayBothClocks();
16124     adjustedClock = TRUE;
16125 }
16126
16127 /* Stop clocks and reset to a fresh time control */
16128 void
16129 ResetClocks ()
16130 {
16131     (void) StopClockTimer();
16132     if (appData.icsActive) {
16133         whiteTimeRemaining = blackTimeRemaining = 0;
16134     } else if (searchTime) {
16135         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16136         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16137     } else { /* [HGM] correct new time quote for time odds */
16138         whiteTC = blackTC = fullTimeControlString;
16139         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16140         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16141     }
16142     if (whiteFlag || blackFlag) {
16143         DisplayTitle("");
16144         whiteFlag = blackFlag = FALSE;
16145     }
16146     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16147     DisplayBothClocks();
16148     adjustedClock = FALSE;
16149 }
16150
16151 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16152
16153 /* Decrement running clock by amount of time that has passed */
16154 void
16155 DecrementClocks ()
16156 {
16157     long timeRemaining;
16158     long lastTickLength, fudge;
16159     TimeMark now;
16160
16161     if (!appData.clockMode) return;
16162     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16163
16164     GetTimeMark(&now);
16165
16166     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16167
16168     /* Fudge if we woke up a little too soon */
16169     fudge = intendedTickLength - lastTickLength;
16170     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16171
16172     if (WhiteOnMove(forwardMostMove)) {
16173         if(whiteNPS >= 0) lastTickLength = 0;
16174         timeRemaining = whiteTimeRemaining -= lastTickLength;
16175         if(timeRemaining < 0 && !appData.icsActive) {
16176             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16177             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16178                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16179                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16180             }
16181         }
16182         DisplayWhiteClock(whiteTimeRemaining - fudge,
16183                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16184     } else {
16185         if(blackNPS >= 0) lastTickLength = 0;
16186         timeRemaining = blackTimeRemaining -= lastTickLength;
16187         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16188             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16189             if(suddenDeath) {
16190                 blackStartMove = forwardMostMove;
16191                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16192             }
16193         }
16194         DisplayBlackClock(blackTimeRemaining - fudge,
16195                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16196     }
16197     if (CheckFlags()) return;
16198
16199     tickStartTM = now;
16200     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16201     StartClockTimer(intendedTickLength);
16202
16203     /* if the time remaining has fallen below the alarm threshold, sound the
16204      * alarm. if the alarm has sounded and (due to a takeback or time control
16205      * with increment) the time remaining has increased to a level above the
16206      * threshold, reset the alarm so it can sound again.
16207      */
16208
16209     if (appData.icsActive && appData.icsAlarm) {
16210
16211         /* make sure we are dealing with the user's clock */
16212         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16213                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16214            )) return;
16215
16216         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16217             alarmSounded = FALSE;
16218         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16219             PlayAlarmSound();
16220             alarmSounded = TRUE;
16221         }
16222     }
16223 }
16224
16225
16226 /* A player has just moved, so stop the previously running
16227    clock and (if in clock mode) start the other one.
16228    We redisplay both clocks in case we're in ICS mode, because
16229    ICS gives us an update to both clocks after every move.
16230    Note that this routine is called *after* forwardMostMove
16231    is updated, so the last fractional tick must be subtracted
16232    from the color that is *not* on move now.
16233 */
16234 void
16235 SwitchClocks (int newMoveNr)
16236 {
16237     long lastTickLength;
16238     TimeMark now;
16239     int flagged = FALSE;
16240
16241     GetTimeMark(&now);
16242
16243     if (StopClockTimer() && appData.clockMode) {
16244         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16245         if (!WhiteOnMove(forwardMostMove)) {
16246             if(blackNPS >= 0) lastTickLength = 0;
16247             blackTimeRemaining -= lastTickLength;
16248            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16249 //         if(pvInfoList[forwardMostMove].time == -1)
16250                  pvInfoList[forwardMostMove].time =               // use GUI time
16251                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16252         } else {
16253            if(whiteNPS >= 0) lastTickLength = 0;
16254            whiteTimeRemaining -= lastTickLength;
16255            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16256 //         if(pvInfoList[forwardMostMove].time == -1)
16257                  pvInfoList[forwardMostMove].time =
16258                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16259         }
16260         flagged = CheckFlags();
16261     }
16262     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16263     CheckTimeControl();
16264
16265     if (flagged || !appData.clockMode) return;
16266
16267     switch (gameMode) {
16268       case MachinePlaysBlack:
16269       case MachinePlaysWhite:
16270       case BeginningOfGame:
16271         if (pausing) return;
16272         break;
16273
16274       case EditGame:
16275       case PlayFromGameFile:
16276       case IcsExamining:
16277         return;
16278
16279       default:
16280         break;
16281     }
16282
16283     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16284         if(WhiteOnMove(forwardMostMove))
16285              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16286         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16287     }
16288
16289     tickStartTM = now;
16290     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16291       whiteTimeRemaining : blackTimeRemaining);
16292     StartClockTimer(intendedTickLength);
16293 }
16294
16295
16296 /* Stop both clocks */
16297 void
16298 StopClocks ()
16299 {
16300     long lastTickLength;
16301     TimeMark now;
16302
16303     if (!StopClockTimer()) return;
16304     if (!appData.clockMode) return;
16305
16306     GetTimeMark(&now);
16307
16308     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16309     if (WhiteOnMove(forwardMostMove)) {
16310         if(whiteNPS >= 0) lastTickLength = 0;
16311         whiteTimeRemaining -= lastTickLength;
16312         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16313     } else {
16314         if(blackNPS >= 0) lastTickLength = 0;
16315         blackTimeRemaining -= lastTickLength;
16316         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16317     }
16318     CheckFlags();
16319 }
16320
16321 /* Start clock of player on move.  Time may have been reset, so
16322    if clock is already running, stop and restart it. */
16323 void
16324 StartClocks ()
16325 {
16326     (void) StopClockTimer(); /* in case it was running already */
16327     DisplayBothClocks();
16328     if (CheckFlags()) return;
16329
16330     if (!appData.clockMode) return;
16331     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16332
16333     GetTimeMark(&tickStartTM);
16334     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16335       whiteTimeRemaining : blackTimeRemaining);
16336
16337    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16338     whiteNPS = blackNPS = -1;
16339     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16340        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16341         whiteNPS = first.nps;
16342     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16343        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16344         blackNPS = first.nps;
16345     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16346         whiteNPS = second.nps;
16347     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16348         blackNPS = second.nps;
16349     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16350
16351     StartClockTimer(intendedTickLength);
16352 }
16353
16354 char *
16355 TimeString (long ms)
16356 {
16357     long second, minute, hour, day;
16358     char *sign = "";
16359     static char buf[32];
16360
16361     if (ms > 0 && ms <= 9900) {
16362       /* convert milliseconds to tenths, rounding up */
16363       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16364
16365       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16366       return buf;
16367     }
16368
16369     /* convert milliseconds to seconds, rounding up */
16370     /* use floating point to avoid strangeness of integer division
16371        with negative dividends on many machines */
16372     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16373
16374     if (second < 0) {
16375         sign = "-";
16376         second = -second;
16377     }
16378
16379     day = second / (60 * 60 * 24);
16380     second = second % (60 * 60 * 24);
16381     hour = second / (60 * 60);
16382     second = second % (60 * 60);
16383     minute = second / 60;
16384     second = second % 60;
16385
16386     if (day > 0)
16387       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16388               sign, day, hour, minute, second);
16389     else if (hour > 0)
16390       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16391     else
16392       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16393
16394     return buf;
16395 }
16396
16397
16398 /*
16399  * This is necessary because some C libraries aren't ANSI C compliant yet.
16400  */
16401 char *
16402 StrStr (char *string, char *match)
16403 {
16404     int i, length;
16405
16406     length = strlen(match);
16407
16408     for (i = strlen(string) - length; i >= 0; i--, string++)
16409       if (!strncmp(match, string, length))
16410         return string;
16411
16412     return NULL;
16413 }
16414
16415 char *
16416 StrCaseStr (char *string, char *match)
16417 {
16418     int i, j, length;
16419
16420     length = strlen(match);
16421
16422     for (i = strlen(string) - length; i >= 0; i--, string++) {
16423         for (j = 0; j < length; j++) {
16424             if (ToLower(match[j]) != ToLower(string[j]))
16425               break;
16426         }
16427         if (j == length) return string;
16428     }
16429
16430     return NULL;
16431 }
16432
16433 #ifndef _amigados
16434 int
16435 StrCaseCmp (char *s1, char *s2)
16436 {
16437     char c1, c2;
16438
16439     for (;;) {
16440         c1 = ToLower(*s1++);
16441         c2 = ToLower(*s2++);
16442         if (c1 > c2) return 1;
16443         if (c1 < c2) return -1;
16444         if (c1 == NULLCHAR) return 0;
16445     }
16446 }
16447
16448
16449 int
16450 ToLower (int c)
16451 {
16452     return isupper(c) ? tolower(c) : c;
16453 }
16454
16455
16456 int
16457 ToUpper (int c)
16458 {
16459     return islower(c) ? toupper(c) : c;
16460 }
16461 #endif /* !_amigados    */
16462
16463 char *
16464 StrSave (char *s)
16465 {
16466   char *ret;
16467
16468   if ((ret = (char *) malloc(strlen(s) + 1)))
16469     {
16470       safeStrCpy(ret, s, strlen(s)+1);
16471     }
16472   return ret;
16473 }
16474
16475 char *
16476 StrSavePtr (char *s, char **savePtr)
16477 {
16478     if (*savePtr) {
16479         free(*savePtr);
16480     }
16481     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16482       safeStrCpy(*savePtr, s, strlen(s)+1);
16483     }
16484     return(*savePtr);
16485 }
16486
16487 char *
16488 PGNDate ()
16489 {
16490     time_t clock;
16491     struct tm *tm;
16492     char buf[MSG_SIZ];
16493
16494     clock = time((time_t *)NULL);
16495     tm = localtime(&clock);
16496     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16497             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16498     return StrSave(buf);
16499 }
16500
16501
16502 char *
16503 PositionToFEN (int move, char *overrideCastling)
16504 {
16505     int i, j, fromX, fromY, toX, toY;
16506     int whiteToPlay;
16507     char buf[MSG_SIZ];
16508     char *p, *q;
16509     int emptycount;
16510     ChessSquare piece;
16511
16512     whiteToPlay = (gameMode == EditPosition) ?
16513       !blackPlaysFirst : (move % 2 == 0);
16514     p = buf;
16515
16516     /* Piece placement data */
16517     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16518         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16519         emptycount = 0;
16520         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16521             if (boards[move][i][j] == EmptySquare) {
16522                 emptycount++;
16523             } else { ChessSquare piece = boards[move][i][j];
16524                 if (emptycount > 0) {
16525                     if(emptycount<10) /* [HGM] can be >= 10 */
16526                         *p++ = '0' + emptycount;
16527                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16528                     emptycount = 0;
16529                 }
16530                 if(PieceToChar(piece) == '+') {
16531                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16532                     *p++ = '+';
16533                     piece = (ChessSquare)(DEMOTED piece);
16534                 }
16535                 *p++ = PieceToChar(piece);
16536                 if(p[-1] == '~') {
16537                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16538                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16539                     *p++ = '~';
16540                 }
16541             }
16542         }
16543         if (emptycount > 0) {
16544             if(emptycount<10) /* [HGM] can be >= 10 */
16545                 *p++ = '0' + emptycount;
16546             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16547             emptycount = 0;
16548         }
16549         *p++ = '/';
16550     }
16551     *(p - 1) = ' ';
16552
16553     /* [HGM] print Crazyhouse or Shogi holdings */
16554     if( gameInfo.holdingsWidth ) {
16555         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16556         q = p;
16557         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16558             piece = boards[move][i][BOARD_WIDTH-1];
16559             if( piece != EmptySquare )
16560               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16561                   *p++ = PieceToChar(piece);
16562         }
16563         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16564             piece = boards[move][BOARD_HEIGHT-i-1][0];
16565             if( piece != EmptySquare )
16566               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16567                   *p++ = PieceToChar(piece);
16568         }
16569
16570         if( q == p ) *p++ = '-';
16571         *p++ = ']';
16572         *p++ = ' ';
16573     }
16574
16575     /* Active color */
16576     *p++ = whiteToPlay ? 'w' : 'b';
16577     *p++ = ' ';
16578
16579   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16580     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16581   } else {
16582   if(nrCastlingRights) {
16583      q = p;
16584      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16585        /* [HGM] write directly from rights */
16586            if(boards[move][CASTLING][2] != NoRights &&
16587               boards[move][CASTLING][0] != NoRights   )
16588                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16589            if(boards[move][CASTLING][2] != NoRights &&
16590               boards[move][CASTLING][1] != NoRights   )
16591                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16592            if(boards[move][CASTLING][5] != NoRights &&
16593               boards[move][CASTLING][3] != NoRights   )
16594                 *p++ = boards[move][CASTLING][3] + AAA;
16595            if(boards[move][CASTLING][5] != NoRights &&
16596               boards[move][CASTLING][4] != NoRights   )
16597                 *p++ = boards[move][CASTLING][4] + AAA;
16598      } else {
16599
16600         /* [HGM] write true castling rights */
16601         if( nrCastlingRights == 6 ) {
16602             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16603                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16604             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16605                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16606             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16607                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16608             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16609                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16610         }
16611      }
16612      if (q == p) *p++ = '-'; /* No castling rights */
16613      *p++ = ' ';
16614   }
16615
16616   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16617      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16618     /* En passant target square */
16619     if (move > backwardMostMove) {
16620         fromX = moveList[move - 1][0] - AAA;
16621         fromY = moveList[move - 1][1] - ONE;
16622         toX = moveList[move - 1][2] - AAA;
16623         toY = moveList[move - 1][3] - ONE;
16624         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16625             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16626             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16627             fromX == toX) {
16628             /* 2-square pawn move just happened */
16629             *p++ = toX + AAA;
16630             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16631         } else {
16632             *p++ = '-';
16633         }
16634     } else if(move == backwardMostMove) {
16635         // [HGM] perhaps we should always do it like this, and forget the above?
16636         if((signed char)boards[move][EP_STATUS] >= 0) {
16637             *p++ = boards[move][EP_STATUS] + AAA;
16638             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16639         } else {
16640             *p++ = '-';
16641         }
16642     } else {
16643         *p++ = '-';
16644     }
16645     *p++ = ' ';
16646   }
16647   }
16648
16649     /* [HGM] find reversible plies */
16650     {   int i = 0, j=move;
16651
16652         if (appData.debugMode) { int k;
16653             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16654             for(k=backwardMostMove; k<=forwardMostMove; k++)
16655                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16656
16657         }
16658
16659         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16660         if( j == backwardMostMove ) i += initialRulePlies;
16661         sprintf(p, "%d ", i);
16662         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16663     }
16664     /* Fullmove number */
16665     sprintf(p, "%d", (move / 2) + 1);
16666
16667     return StrSave(buf);
16668 }
16669
16670 Boolean
16671 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16672 {
16673     int i, j;
16674     char *p, c;
16675     int emptycount;
16676     ChessSquare piece;
16677
16678     p = fen;
16679
16680     /* [HGM] by default clear Crazyhouse holdings, if present */
16681     if(gameInfo.holdingsWidth) {
16682        for(i=0; i<BOARD_HEIGHT; i++) {
16683            board[i][0]             = EmptySquare; /* black holdings */
16684            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16685            board[i][1]             = (ChessSquare) 0; /* black counts */
16686            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16687        }
16688     }
16689
16690     /* Piece placement data */
16691     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16692         j = 0;
16693         for (;;) {
16694             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16695                 if (*p == '/') p++;
16696                 emptycount = gameInfo.boardWidth - j;
16697                 while (emptycount--)
16698                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16699                 break;
16700 #if(BOARD_FILES >= 10)
16701             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16702                 p++; emptycount=10;
16703                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16704                 while (emptycount--)
16705                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16706 #endif
16707             } else if (isdigit(*p)) {
16708                 emptycount = *p++ - '0';
16709                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16710                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16711                 while (emptycount--)
16712                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16713             } else if (*p == '+' || isalpha(*p)) {
16714                 if (j >= gameInfo.boardWidth) return FALSE;
16715                 if(*p=='+') {
16716                     piece = CharToPiece(*++p);
16717                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16718                     piece = (ChessSquare) (PROMOTED piece ); p++;
16719                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16720                 } else piece = CharToPiece(*p++);
16721
16722                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16723                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16724                     piece = (ChessSquare) (PROMOTED piece);
16725                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16726                     p++;
16727                 }
16728                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16729             } else {
16730                 return FALSE;
16731             }
16732         }
16733     }
16734     while (*p == '/' || *p == ' ') p++;
16735
16736     /* [HGM] look for Crazyhouse holdings here */
16737     while(*p==' ') p++;
16738     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16739         if(*p == '[') p++;
16740         if(*p == '-' ) p++; /* empty holdings */ else {
16741             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16742             /* if we would allow FEN reading to set board size, we would   */
16743             /* have to add holdings and shift the board read so far here   */
16744             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16745                 p++;
16746                 if((int) piece >= (int) BlackPawn ) {
16747                     i = (int)piece - (int)BlackPawn;
16748                     i = PieceToNumber((ChessSquare)i);
16749                     if( i >= gameInfo.holdingsSize ) return FALSE;
16750                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16751                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16752                 } else {
16753                     i = (int)piece - (int)WhitePawn;
16754                     i = PieceToNumber((ChessSquare)i);
16755                     if( i >= gameInfo.holdingsSize ) return FALSE;
16756                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16757                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16758                 }
16759             }
16760         }
16761         if(*p == ']') p++;
16762     }
16763
16764     while(*p == ' ') p++;
16765
16766     /* Active color */
16767     c = *p++;
16768     if(appData.colorNickNames) {
16769       if( c == appData.colorNickNames[0] ) c = 'w'; else
16770       if( c == appData.colorNickNames[1] ) c = 'b';
16771     }
16772     switch (c) {
16773       case 'w':
16774         *blackPlaysFirst = FALSE;
16775         break;
16776       case 'b':
16777         *blackPlaysFirst = TRUE;
16778         break;
16779       default:
16780         return FALSE;
16781     }
16782
16783     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16784     /* return the extra info in global variiables             */
16785
16786     /* set defaults in case FEN is incomplete */
16787     board[EP_STATUS] = EP_UNKNOWN;
16788     for(i=0; i<nrCastlingRights; i++ ) {
16789         board[CASTLING][i] =
16790             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16791     }   /* assume possible unless obviously impossible */
16792     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16793     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16794     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16795                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16796     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16797     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16798     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16799                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16800     FENrulePlies = 0;
16801
16802     while(*p==' ') p++;
16803     if(nrCastlingRights) {
16804       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16805           /* castling indicator present, so default becomes no castlings */
16806           for(i=0; i<nrCastlingRights; i++ ) {
16807                  board[CASTLING][i] = NoRights;
16808           }
16809       }
16810       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16811              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16812              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16813              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16814         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16815
16816         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16817             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16818             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16819         }
16820         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16821             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16822         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16823                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16824         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16825                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16826         switch(c) {
16827           case'K':
16828               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16829               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16830               board[CASTLING][2] = whiteKingFile;
16831               break;
16832           case'Q':
16833               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16834               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16835               board[CASTLING][2] = whiteKingFile;
16836               break;
16837           case'k':
16838               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16839               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16840               board[CASTLING][5] = blackKingFile;
16841               break;
16842           case'q':
16843               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16844               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16845               board[CASTLING][5] = blackKingFile;
16846           case '-':
16847               break;
16848           default: /* FRC castlings */
16849               if(c >= 'a') { /* black rights */
16850                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16851                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16852                   if(i == BOARD_RGHT) break;
16853                   board[CASTLING][5] = i;
16854                   c -= AAA;
16855                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16856                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16857                   if(c > i)
16858                       board[CASTLING][3] = c;
16859                   else
16860                       board[CASTLING][4] = c;
16861               } else { /* white rights */
16862                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16863                     if(board[0][i] == WhiteKing) break;
16864                   if(i == BOARD_RGHT) break;
16865                   board[CASTLING][2] = i;
16866                   c -= AAA - 'a' + 'A';
16867                   if(board[0][c] >= WhiteKing) break;
16868                   if(c > i)
16869                       board[CASTLING][0] = c;
16870                   else
16871                       board[CASTLING][1] = c;
16872               }
16873         }
16874       }
16875       for(i=0; i<nrCastlingRights; i++)
16876         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16877     if (appData.debugMode) {
16878         fprintf(debugFP, "FEN castling rights:");
16879         for(i=0; i<nrCastlingRights; i++)
16880         fprintf(debugFP, " %d", board[CASTLING][i]);
16881         fprintf(debugFP, "\n");
16882     }
16883
16884       while(*p==' ') p++;
16885     }
16886
16887     /* read e.p. field in games that know e.p. capture */
16888     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16889        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16890       if(*p=='-') {
16891         p++; board[EP_STATUS] = EP_NONE;
16892       } else {
16893          char c = *p++ - AAA;
16894
16895          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16896          if(*p >= '0' && *p <='9') p++;
16897          board[EP_STATUS] = c;
16898       }
16899     }
16900
16901
16902     if(sscanf(p, "%d", &i) == 1) {
16903         FENrulePlies = i; /* 50-move ply counter */
16904         /* (The move number is still ignored)    */
16905     }
16906
16907     return TRUE;
16908 }
16909
16910 void
16911 EditPositionPasteFEN (char *fen)
16912 {
16913   if (fen != NULL) {
16914     Board initial_position;
16915
16916     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16917       DisplayError(_("Bad FEN position in clipboard"), 0);
16918       return ;
16919     } else {
16920       int savedBlackPlaysFirst = blackPlaysFirst;
16921       EditPositionEvent();
16922       blackPlaysFirst = savedBlackPlaysFirst;
16923       CopyBoard(boards[0], initial_position);
16924       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16925       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16926       DisplayBothClocks();
16927       DrawPosition(FALSE, boards[currentMove]);
16928     }
16929   }
16930 }
16931
16932 static char cseq[12] = "\\   ";
16933
16934 Boolean
16935 set_cont_sequence (char *new_seq)
16936 {
16937     int len;
16938     Boolean ret;
16939
16940     // handle bad attempts to set the sequence
16941         if (!new_seq)
16942                 return 0; // acceptable error - no debug
16943
16944     len = strlen(new_seq);
16945     ret = (len > 0) && (len < sizeof(cseq));
16946     if (ret)
16947       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16948     else if (appData.debugMode)
16949       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16950     return ret;
16951 }
16952
16953 /*
16954     reformat a source message so words don't cross the width boundary.  internal
16955     newlines are not removed.  returns the wrapped size (no null character unless
16956     included in source message).  If dest is NULL, only calculate the size required
16957     for the dest buffer.  lp argument indicats line position upon entry, and it's
16958     passed back upon exit.
16959 */
16960 int
16961 wrap (char *dest, char *src, int count, int width, int *lp)
16962 {
16963     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16964
16965     cseq_len = strlen(cseq);
16966     old_line = line = *lp;
16967     ansi = len = clen = 0;
16968
16969     for (i=0; i < count; i++)
16970     {
16971         if (src[i] == '\033')
16972             ansi = 1;
16973
16974         // if we hit the width, back up
16975         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16976         {
16977             // store i & len in case the word is too long
16978             old_i = i, old_len = len;
16979
16980             // find the end of the last word
16981             while (i && src[i] != ' ' && src[i] != '\n')
16982             {
16983                 i--;
16984                 len--;
16985             }
16986
16987             // word too long?  restore i & len before splitting it
16988             if ((old_i-i+clen) >= width)
16989             {
16990                 i = old_i;
16991                 len = old_len;
16992             }
16993
16994             // extra space?
16995             if (i && src[i-1] == ' ')
16996                 len--;
16997
16998             if (src[i] != ' ' && src[i] != '\n')
16999             {
17000                 i--;
17001                 if (len)
17002                     len--;
17003             }
17004
17005             // now append the newline and continuation sequence
17006             if (dest)
17007                 dest[len] = '\n';
17008             len++;
17009             if (dest)
17010                 strncpy(dest+len, cseq, cseq_len);
17011             len += cseq_len;
17012             line = cseq_len;
17013             clen = cseq_len;
17014             continue;
17015         }
17016
17017         if (dest)
17018             dest[len] = src[i];
17019         len++;
17020         if (!ansi)
17021             line++;
17022         if (src[i] == '\n')
17023             line = 0;
17024         if (src[i] == 'm')
17025             ansi = 0;
17026     }
17027     if (dest && appData.debugMode)
17028     {
17029         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17030             count, width, line, len, *lp);
17031         show_bytes(debugFP, src, count);
17032         fprintf(debugFP, "\ndest: ");
17033         show_bytes(debugFP, dest, len);
17034         fprintf(debugFP, "\n");
17035     }
17036     *lp = dest ? line : old_line;
17037
17038     return len;
17039 }
17040
17041 // [HGM] vari: routines for shelving variations
17042 Boolean modeRestore = FALSE;
17043
17044 void
17045 PushInner (int firstMove, int lastMove)
17046 {
17047         int i, j, nrMoves = lastMove - firstMove;
17048
17049         // push current tail of game on stack
17050         savedResult[storedGames] = gameInfo.result;
17051         savedDetails[storedGames] = gameInfo.resultDetails;
17052         gameInfo.resultDetails = NULL;
17053         savedFirst[storedGames] = firstMove;
17054         savedLast [storedGames] = lastMove;
17055         savedFramePtr[storedGames] = framePtr;
17056         framePtr -= nrMoves; // reserve space for the boards
17057         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17058             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17059             for(j=0; j<MOVE_LEN; j++)
17060                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17061             for(j=0; j<2*MOVE_LEN; j++)
17062                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17063             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17064             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17065             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17066             pvInfoList[firstMove+i-1].depth = 0;
17067             commentList[framePtr+i] = commentList[firstMove+i];
17068             commentList[firstMove+i] = NULL;
17069         }
17070
17071         storedGames++;
17072         forwardMostMove = firstMove; // truncate game so we can start variation
17073 }
17074
17075 void
17076 PushTail (int firstMove, int lastMove)
17077 {
17078         if(appData.icsActive) { // only in local mode
17079                 forwardMostMove = currentMove; // mimic old ICS behavior
17080                 return;
17081         }
17082         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17083
17084         PushInner(firstMove, lastMove);
17085         if(storedGames == 1) GreyRevert(FALSE);
17086         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17087 }
17088
17089 void
17090 PopInner (Boolean annotate)
17091 {
17092         int i, j, nrMoves;
17093         char buf[8000], moveBuf[20];
17094
17095         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17096         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17097         nrMoves = savedLast[storedGames] - currentMove;
17098         if(annotate) {
17099                 int cnt = 10;
17100                 if(!WhiteOnMove(currentMove))
17101                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17102                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17103                 for(i=currentMove; i<forwardMostMove; i++) {
17104                         if(WhiteOnMove(i))
17105                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17106                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17107                         strcat(buf, moveBuf);
17108                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17109                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17110                 }
17111                 strcat(buf, ")");
17112         }
17113         for(i=1; i<=nrMoves; i++) { // copy last variation back
17114             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17115             for(j=0; j<MOVE_LEN; j++)
17116                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17117             for(j=0; j<2*MOVE_LEN; j++)
17118                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17119             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17120             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17121             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17122             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17123             commentList[currentMove+i] = commentList[framePtr+i];
17124             commentList[framePtr+i] = NULL;
17125         }
17126         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17127         framePtr = savedFramePtr[storedGames];
17128         gameInfo.result = savedResult[storedGames];
17129         if(gameInfo.resultDetails != NULL) {
17130             free(gameInfo.resultDetails);
17131       }
17132         gameInfo.resultDetails = savedDetails[storedGames];
17133         forwardMostMove = currentMove + nrMoves;
17134 }
17135
17136 Boolean
17137 PopTail (Boolean annotate)
17138 {
17139         if(appData.icsActive) return FALSE; // only in local mode
17140         if(!storedGames) return FALSE; // sanity
17141         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17142
17143         PopInner(annotate);
17144         if(currentMove < forwardMostMove) ForwardEvent(); else
17145         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17146
17147         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17148         return TRUE;
17149 }
17150
17151 void
17152 CleanupTail ()
17153 {       // remove all shelved variations
17154         int i;
17155         for(i=0; i<storedGames; i++) {
17156             if(savedDetails[i])
17157                 free(savedDetails[i]);
17158             savedDetails[i] = NULL;
17159         }
17160         for(i=framePtr; i<MAX_MOVES; i++) {
17161                 if(commentList[i]) free(commentList[i]);
17162                 commentList[i] = NULL;
17163         }
17164         framePtr = MAX_MOVES-1;
17165         storedGames = 0;
17166 }
17167
17168 void
17169 LoadVariation (int index, char *text)
17170 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17171         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17172         int level = 0, move;
17173
17174         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17175         // first find outermost bracketing variation
17176         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17177             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17178                 if(*p == '{') wait = '}'; else
17179                 if(*p == '[') wait = ']'; else
17180                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17181                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17182             }
17183             if(*p == wait) wait = NULLCHAR; // closing ]} found
17184             p++;
17185         }
17186         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17187         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17188         end[1] = NULLCHAR; // clip off comment beyond variation
17189         ToNrEvent(currentMove-1);
17190         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17191         // kludge: use ParsePV() to append variation to game
17192         move = currentMove;
17193         ParsePV(start, TRUE, TRUE);
17194         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17195         ClearPremoveHighlights();
17196         CommentPopDown();
17197         ToNrEvent(currentMove+1);
17198 }
17199