Install engine within current group
[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     int j;
6135     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6136     excludePtr = 24; exCnt = 0;
6137     WriteMap(0);
6138 }
6139
6140 static void
6141 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6142 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6143     char buf[2*MOVE_LEN], *p;
6144     Exclusion *e = excluTab;
6145     int i;
6146     for(i=0; i<exCnt; i++)
6147         if(e[i].ff == fromX && e[i].fr == fromY &&
6148            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6149     if(i == exCnt) { // was not in exclude list; add it
6150         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6151         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6152             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6153             return; // abort
6154         }
6155         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6156         excludePtr++; e[i].mark = excludePtr++;
6157         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6158         exCnt++;
6159     }
6160     exclusionHeader[e[i].mark] = state;
6161 }
6162
6163 static int
6164 ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, char state)
6165 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6166     char *p, buf[MSG_SIZ];
6167     int j, k;
6168     ChessMove moveType;
6169     if(promoChar == -1) { // kludge to indicate best move
6170         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6171             return 1; // if unparsable, abort
6172     }
6173     // update exclusion map (resolving toggle by consulting existing state)
6174     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6175     j = k%8; k >>= 3;
6176     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6177     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6178          excludeMap[k] |=   1<<j;
6179     else excludeMap[k] &= ~(1<<j);
6180     // update header
6181     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6182     // inform engine
6183     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6184     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6185     SendToProgram(buf, &first);
6186     return (state == '+');
6187 }
6188
6189 static void
6190 ExcludeClick (int index)
6191 {
6192     int i, j;
6193     char buf[MSG_SIZ];
6194     Exclusion *e = excluTab;
6195     if(index < 25) { // none, best or tail clicked
6196         if(index < 13) { // none: include all
6197             WriteMap(0); // clear map
6198             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6199             SendToProgram("include all\n", &first); // and inform engine
6200         } else if(index > 18) { // tail
6201             if(exclusionHeader[19] == '-') { // tail was excluded
6202                 SendToProgram("include all\n", &first);
6203                 WriteMap(0); // clear map completely
6204                 // now re-exclude selected moves
6205                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6206                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6207             } else { // tail was included or in mixed state
6208                 SendToProgram("exclude all\n", &first);
6209                 WriteMap(0xFF); // fill map completely
6210                 // now re-include selected moves
6211                 j = 0; // count them
6212                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6213                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6214                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6215             }
6216         } else { // best
6217             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6218         }
6219     } else {
6220         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6221             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6222             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6223             break;
6224         }
6225     }
6226 }
6227
6228 ChessSquare
6229 DefaultPromoChoice (int white)
6230 {
6231     ChessSquare result;
6232     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6233         result = WhiteFerz; // no choice
6234     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6235         result= WhiteKing; // in Suicide Q is the last thing we want
6236     else if(gameInfo.variant == VariantSpartan)
6237         result = white ? WhiteQueen : WhiteAngel;
6238     else result = WhiteQueen;
6239     if(!white) result = WHITE_TO_BLACK result;
6240     return result;
6241 }
6242
6243 static int autoQueen; // [HGM] oneclick
6244
6245 int
6246 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6247 {
6248     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6249     /* [HGM] add Shogi promotions */
6250     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6251     ChessSquare piece;
6252     ChessMove moveType;
6253     Boolean premove;
6254
6255     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6256     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6257
6258     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6259       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6260         return FALSE;
6261
6262     piece = boards[currentMove][fromY][fromX];
6263     if(gameInfo.variant == VariantShogi) {
6264         promotionZoneSize = BOARD_HEIGHT/3;
6265         highestPromotingPiece = (int)WhiteFerz;
6266     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6267         promotionZoneSize = 3;
6268     }
6269
6270     // Treat Lance as Pawn when it is not representing Amazon
6271     if(gameInfo.variant != VariantSuper) {
6272         if(piece == WhiteLance) piece = WhitePawn; else
6273         if(piece == BlackLance) piece = BlackPawn;
6274     }
6275
6276     // next weed out all moves that do not touch the promotion zone at all
6277     if((int)piece >= BlackPawn) {
6278         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6279              return FALSE;
6280         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6281     } else {
6282         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6283            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6284     }
6285
6286     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6287
6288     // weed out mandatory Shogi promotions
6289     if(gameInfo.variant == VariantShogi) {
6290         if(piece >= BlackPawn) {
6291             if(toY == 0 && piece == BlackPawn ||
6292                toY == 0 && piece == BlackQueen ||
6293                toY <= 1 && piece == BlackKnight) {
6294                 *promoChoice = '+';
6295                 return FALSE;
6296             }
6297         } else {
6298             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6299                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6300                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6301                 *promoChoice = '+';
6302                 return FALSE;
6303             }
6304         }
6305     }
6306
6307     // weed out obviously illegal Pawn moves
6308     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6309         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6310         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6311         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6312         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6313         // note we are not allowed to test for valid (non-)capture, due to premove
6314     }
6315
6316     // we either have a choice what to promote to, or (in Shogi) whether to promote
6317     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6318         *promoChoice = PieceToChar(BlackFerz);  // no choice
6319         return FALSE;
6320     }
6321     // no sense asking what we must promote to if it is going to explode...
6322     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6323         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6324         return FALSE;
6325     }
6326     // give caller the default choice even if we will not make it
6327     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6328     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6329     if(        sweepSelect && gameInfo.variant != VariantGreat
6330                            && gameInfo.variant != VariantGrand
6331                            && gameInfo.variant != VariantSuper) return FALSE;
6332     if(autoQueen) return FALSE; // predetermined
6333
6334     // suppress promotion popup on illegal moves that are not premoves
6335     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6336               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6337     if(appData.testLegality && !premove) {
6338         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6339                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6340         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6341             return FALSE;
6342     }
6343
6344     return TRUE;
6345 }
6346
6347 int
6348 InPalace (int row, int column)
6349 {   /* [HGM] for Xiangqi */
6350     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6351          column < (BOARD_WIDTH + 4)/2 &&
6352          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6353     return FALSE;
6354 }
6355
6356 int
6357 PieceForSquare (int x, int y)
6358 {
6359   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6360      return -1;
6361   else
6362      return boards[currentMove][y][x];
6363 }
6364
6365 int
6366 OKToStartUserMove (int x, int y)
6367 {
6368     ChessSquare from_piece;
6369     int white_piece;
6370
6371     if (matchMode) return FALSE;
6372     if (gameMode == EditPosition) return TRUE;
6373
6374     if (x >= 0 && y >= 0)
6375       from_piece = boards[currentMove][y][x];
6376     else
6377       from_piece = EmptySquare;
6378
6379     if (from_piece == EmptySquare) return FALSE;
6380
6381     white_piece = (int)from_piece >= (int)WhitePawn &&
6382       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6383
6384     switch (gameMode) {
6385       case AnalyzeFile:
6386       case TwoMachinesPlay:
6387       case EndOfGame:
6388         return FALSE;
6389
6390       case IcsObserving:
6391       case IcsIdle:
6392         return FALSE;
6393
6394       case MachinePlaysWhite:
6395       case IcsPlayingBlack:
6396         if (appData.zippyPlay) return FALSE;
6397         if (white_piece) {
6398             DisplayMoveError(_("You are playing Black"));
6399             return FALSE;
6400         }
6401         break;
6402
6403       case MachinePlaysBlack:
6404       case IcsPlayingWhite:
6405         if (appData.zippyPlay) return FALSE;
6406         if (!white_piece) {
6407             DisplayMoveError(_("You are playing White"));
6408             return FALSE;
6409         }
6410         break;
6411
6412       case PlayFromGameFile:
6413             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6414       case EditGame:
6415         if (!white_piece && WhiteOnMove(currentMove)) {
6416             DisplayMoveError(_("It is White's turn"));
6417             return FALSE;
6418         }
6419         if (white_piece && !WhiteOnMove(currentMove)) {
6420             DisplayMoveError(_("It is Black's turn"));
6421             return FALSE;
6422         }
6423         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6424             /* Editing correspondence game history */
6425             /* Could disallow this or prompt for confirmation */
6426             cmailOldMove = -1;
6427         }
6428         break;
6429
6430       case BeginningOfGame:
6431         if (appData.icsActive) return FALSE;
6432         if (!appData.noChessProgram) {
6433             if (!white_piece) {
6434                 DisplayMoveError(_("You are playing White"));
6435                 return FALSE;
6436             }
6437         }
6438         break;
6439
6440       case Training:
6441         if (!white_piece && WhiteOnMove(currentMove)) {
6442             DisplayMoveError(_("It is White's turn"));
6443             return FALSE;
6444         }
6445         if (white_piece && !WhiteOnMove(currentMove)) {
6446             DisplayMoveError(_("It is Black's turn"));
6447             return FALSE;
6448         }
6449         break;
6450
6451       default:
6452       case IcsExamining:
6453         break;
6454     }
6455     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6456         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6457         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6458         && gameMode != AnalyzeFile && gameMode != Training) {
6459         DisplayMoveError(_("Displayed position is not current"));
6460         return FALSE;
6461     }
6462     return TRUE;
6463 }
6464
6465 Boolean
6466 OnlyMove (int *x, int *y, Boolean captures) 
6467 {
6468     DisambiguateClosure cl;
6469     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6470     switch(gameMode) {
6471       case MachinePlaysBlack:
6472       case IcsPlayingWhite:
6473       case BeginningOfGame:
6474         if(!WhiteOnMove(currentMove)) return FALSE;
6475         break;
6476       case MachinePlaysWhite:
6477       case IcsPlayingBlack:
6478         if(WhiteOnMove(currentMove)) return FALSE;
6479         break;
6480       case EditGame:
6481         break;
6482       default:
6483         return FALSE;
6484     }
6485     cl.pieceIn = EmptySquare;
6486     cl.rfIn = *y;
6487     cl.ffIn = *x;
6488     cl.rtIn = -1;
6489     cl.ftIn = -1;
6490     cl.promoCharIn = NULLCHAR;
6491     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6492     if( cl.kind == NormalMove ||
6493         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6494         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6495         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6496       fromX = cl.ff;
6497       fromY = cl.rf;
6498       *x = cl.ft;
6499       *y = cl.rt;
6500       return TRUE;
6501     }
6502     if(cl.kind != ImpossibleMove) return FALSE;
6503     cl.pieceIn = EmptySquare;
6504     cl.rfIn = -1;
6505     cl.ffIn = -1;
6506     cl.rtIn = *y;
6507     cl.ftIn = *x;
6508     cl.promoCharIn = NULLCHAR;
6509     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6510     if( cl.kind == NormalMove ||
6511         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6512         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6513         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6514       fromX = cl.ff;
6515       fromY = cl.rf;
6516       *x = cl.ft;
6517       *y = cl.rt;
6518       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6519       return TRUE;
6520     }
6521     return FALSE;
6522 }
6523
6524 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6525 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6526 int lastLoadGameUseList = FALSE;
6527 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6528 ChessMove lastLoadGameStart = EndOfFile;
6529 int doubleClick;
6530
6531 void
6532 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6533 {
6534     ChessMove moveType;
6535     ChessSquare pdown, pup;
6536     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6537
6538
6539     /* Check if the user is playing in turn.  This is complicated because we
6540        let the user "pick up" a piece before it is his turn.  So the piece he
6541        tried to pick up may have been captured by the time he puts it down!
6542        Therefore we use the color the user is supposed to be playing in this
6543        test, not the color of the piece that is currently on the starting
6544        square---except in EditGame mode, where the user is playing both
6545        sides; fortunately there the capture race can't happen.  (It can
6546        now happen in IcsExamining mode, but that's just too bad.  The user
6547        will get a somewhat confusing message in that case.)
6548        */
6549
6550     switch (gameMode) {
6551       case AnalyzeFile:
6552       case TwoMachinesPlay:
6553       case EndOfGame:
6554       case IcsObserving:
6555       case IcsIdle:
6556         /* We switched into a game mode where moves are not accepted,
6557            perhaps while the mouse button was down. */
6558         return;
6559
6560       case MachinePlaysWhite:
6561         /* User is moving for Black */
6562         if (WhiteOnMove(currentMove)) {
6563             DisplayMoveError(_("It is White's turn"));
6564             return;
6565         }
6566         break;
6567
6568       case MachinePlaysBlack:
6569         /* User is moving for White */
6570         if (!WhiteOnMove(currentMove)) {
6571             DisplayMoveError(_("It is Black's turn"));
6572             return;
6573         }
6574         break;
6575
6576       case PlayFromGameFile:
6577             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6578       case EditGame:
6579       case IcsExamining:
6580       case BeginningOfGame:
6581       case AnalyzeMode:
6582       case Training:
6583         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6584         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6585             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6586             /* User is moving for Black */
6587             if (WhiteOnMove(currentMove)) {
6588                 DisplayMoveError(_("It is White's turn"));
6589                 return;
6590             }
6591         } else {
6592             /* User is moving for White */
6593             if (!WhiteOnMove(currentMove)) {
6594                 DisplayMoveError(_("It is Black's turn"));
6595                 return;
6596             }
6597         }
6598         break;
6599
6600       case IcsPlayingBlack:
6601         /* User is moving for Black */
6602         if (WhiteOnMove(currentMove)) {
6603             if (!appData.premove) {
6604                 DisplayMoveError(_("It is White's turn"));
6605             } else if (toX >= 0 && toY >= 0) {
6606                 premoveToX = toX;
6607                 premoveToY = toY;
6608                 premoveFromX = fromX;
6609                 premoveFromY = fromY;
6610                 premovePromoChar = promoChar;
6611                 gotPremove = 1;
6612                 if (appData.debugMode)
6613                     fprintf(debugFP, "Got premove: fromX %d,"
6614                             "fromY %d, toX %d, toY %d\n",
6615                             fromX, fromY, toX, toY);
6616             }
6617             return;
6618         }
6619         break;
6620
6621       case IcsPlayingWhite:
6622         /* User is moving for White */
6623         if (!WhiteOnMove(currentMove)) {
6624             if (!appData.premove) {
6625                 DisplayMoveError(_("It is Black's turn"));
6626             } else if (toX >= 0 && toY >= 0) {
6627                 premoveToX = toX;
6628                 premoveToY = toY;
6629                 premoveFromX = fromX;
6630                 premoveFromY = fromY;
6631                 premovePromoChar = promoChar;
6632                 gotPremove = 1;
6633                 if (appData.debugMode)
6634                     fprintf(debugFP, "Got premove: fromX %d,"
6635                             "fromY %d, toX %d, toY %d\n",
6636                             fromX, fromY, toX, toY);
6637             }
6638             return;
6639         }
6640         break;
6641
6642       default:
6643         break;
6644
6645       case EditPosition:
6646         /* EditPosition, empty square, or different color piece;
6647            click-click move is possible */
6648         if (toX == -2 || toY == -2) {
6649             boards[0][fromY][fromX] = EmptySquare;
6650             DrawPosition(FALSE, boards[currentMove]);
6651             return;
6652         } else if (toX >= 0 && toY >= 0) {
6653             boards[0][toY][toX] = boards[0][fromY][fromX];
6654             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6655                 if(boards[0][fromY][0] != EmptySquare) {
6656                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6657                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6658                 }
6659             } else
6660             if(fromX == BOARD_RGHT+1) {
6661                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6662                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6663                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6664                 }
6665             } else
6666             boards[0][fromY][fromX] = EmptySquare;
6667             DrawPosition(FALSE, boards[currentMove]);
6668             return;
6669         }
6670         return;
6671     }
6672
6673     if(toX < 0 || toY < 0) return;
6674     pdown = boards[currentMove][fromY][fromX];
6675     pup = boards[currentMove][toY][toX];
6676
6677     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6678     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6679          if( pup != EmptySquare ) return;
6680          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6681            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6682                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6683            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6684            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6685            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6686            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6687          fromY = DROP_RANK;
6688     }
6689
6690     /* [HGM] always test for legality, to get promotion info */
6691     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6692                                          fromY, fromX, toY, toX, promoChar);
6693
6694     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6695
6696     /* [HGM] but possibly ignore an IllegalMove result */
6697     if (appData.testLegality) {
6698         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6699             DisplayMoveError(_("Illegal move"));
6700             return;
6701         }
6702     }
6703
6704     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6705         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6706              ClearPremoveHighlights(); // was included
6707         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6708         return;
6709     }
6710
6711     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6712 }
6713
6714 /* Common tail of UserMoveEvent and DropMenuEvent */
6715 int
6716 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6717 {
6718     char *bookHit = 0;
6719
6720     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6721         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6722         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6723         if(WhiteOnMove(currentMove)) {
6724             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6725         } else {
6726             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6727         }
6728     }
6729
6730     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6731        move type in caller when we know the move is a legal promotion */
6732     if(moveType == NormalMove && promoChar)
6733         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6734
6735     /* [HGM] <popupFix> The following if has been moved here from
6736        UserMoveEvent(). Because it seemed to belong here (why not allow
6737        piece drops in training games?), and because it can only be
6738        performed after it is known to what we promote. */
6739     if (gameMode == Training) {
6740       /* compare the move played on the board to the next move in the
6741        * game. If they match, display the move and the opponent's response.
6742        * If they don't match, display an error message.
6743        */
6744       int saveAnimate;
6745       Board testBoard;
6746       CopyBoard(testBoard, boards[currentMove]);
6747       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6748
6749       if (CompareBoards(testBoard, boards[currentMove+1])) {
6750         ForwardInner(currentMove+1);
6751
6752         /* Autoplay the opponent's response.
6753          * if appData.animate was TRUE when Training mode was entered,
6754          * the response will be animated.
6755          */
6756         saveAnimate = appData.animate;
6757         appData.animate = animateTraining;
6758         ForwardInner(currentMove+1);
6759         appData.animate = saveAnimate;
6760
6761         /* check for the end of the game */
6762         if (currentMove >= forwardMostMove) {
6763           gameMode = PlayFromGameFile;
6764           ModeHighlight();
6765           SetTrainingModeOff();
6766           DisplayInformation(_("End of game"));
6767         }
6768       } else {
6769         DisplayError(_("Incorrect move"), 0);
6770       }
6771       return 1;
6772     }
6773
6774   /* Ok, now we know that the move is good, so we can kill
6775      the previous line in Analysis Mode */
6776   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6777                                 && currentMove < forwardMostMove) {
6778     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6779     else forwardMostMove = currentMove;
6780   }
6781
6782   ClearMap();
6783
6784   /* If we need the chess program but it's dead, restart it */
6785   ResurrectChessProgram();
6786
6787   /* A user move restarts a paused game*/
6788   if (pausing)
6789     PauseEvent();
6790
6791   thinkOutput[0] = NULLCHAR;
6792
6793   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6794
6795   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6796     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6797     return 1;
6798   }
6799
6800   if (gameMode == BeginningOfGame) {
6801     if (appData.noChessProgram) {
6802       gameMode = EditGame;
6803       SetGameInfo();
6804     } else {
6805       char buf[MSG_SIZ];
6806       gameMode = MachinePlaysBlack;
6807       StartClocks();
6808       SetGameInfo();
6809       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6810       DisplayTitle(buf);
6811       if (first.sendName) {
6812         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6813         SendToProgram(buf, &first);
6814       }
6815       StartClocks();
6816     }
6817     ModeHighlight();
6818   }
6819
6820   /* Relay move to ICS or chess engine */
6821   if (appData.icsActive) {
6822     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6823         gameMode == IcsExamining) {
6824       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6825         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6826         SendToICS("draw ");
6827         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6828       }
6829       // also send plain move, in case ICS does not understand atomic claims
6830       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6831       ics_user_moved = 1;
6832     }
6833   } else {
6834     if (first.sendTime && (gameMode == BeginningOfGame ||
6835                            gameMode == MachinePlaysWhite ||
6836                            gameMode == MachinePlaysBlack)) {
6837       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6838     }
6839     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6840          // [HGM] book: if program might be playing, let it use book
6841         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6842         first.maybeThinking = TRUE;
6843     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6844         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6845         SendBoard(&first, currentMove+1);
6846     } else SendMoveToProgram(forwardMostMove-1, &first);
6847     if (currentMove == cmailOldMove + 1) {
6848       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6849     }
6850   }
6851
6852   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6853
6854   switch (gameMode) {
6855   case EditGame:
6856     if(appData.testLegality)
6857     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6858     case MT_NONE:
6859     case MT_CHECK:
6860       break;
6861     case MT_CHECKMATE:
6862     case MT_STAINMATE:
6863       if (WhiteOnMove(currentMove)) {
6864         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6865       } else {
6866         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6867       }
6868       break;
6869     case MT_STALEMATE:
6870       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6871       break;
6872     }
6873     break;
6874
6875   case MachinePlaysBlack:
6876   case MachinePlaysWhite:
6877     /* disable certain menu options while machine is thinking */
6878     SetMachineThinkingEnables();
6879     break;
6880
6881   default:
6882     break;
6883   }
6884
6885   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6886   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6887
6888   if(bookHit) { // [HGM] book: simulate book reply
6889         static char bookMove[MSG_SIZ]; // a bit generous?
6890
6891         programStats.nodes = programStats.depth = programStats.time =
6892         programStats.score = programStats.got_only_move = 0;
6893         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6894
6895         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6896         strcat(bookMove, bookHit);
6897         HandleMachineMove(bookMove, &first);
6898   }
6899   return 1;
6900 }
6901
6902 void
6903 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6904 {
6905     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6906     Markers *m = (Markers *) closure;
6907     if(rf == fromY && ff == fromX)
6908         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6909                          || kind == WhiteCapturesEnPassant
6910                          || kind == BlackCapturesEnPassant);
6911     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6912 }
6913
6914 void
6915 MarkTargetSquares (int clear)
6916 {
6917   int x, y;
6918   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6919      !appData.testLegality || gameMode == EditPosition) return;
6920   if(clear) {
6921     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6922   } else {
6923     int capt = 0;
6924     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6925     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6926       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6927       if(capt)
6928       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6929     }
6930   }
6931   DrawPosition(TRUE, NULL);
6932 }
6933
6934 int
6935 Explode (Board board, int fromX, int fromY, int toX, int toY)
6936 {
6937     if(gameInfo.variant == VariantAtomic &&
6938        (board[toY][toX] != EmptySquare ||                     // capture?
6939         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6940                          board[fromY][fromX] == BlackPawn   )
6941       )) {
6942         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6943         return TRUE;
6944     }
6945     return FALSE;
6946 }
6947
6948 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6949
6950 int
6951 CanPromote (ChessSquare piece, int y)
6952 {
6953         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6954         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6955         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6956            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6957            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6958                                                   gameInfo.variant == VariantMakruk) return FALSE;
6959         return (piece == BlackPawn && y == 1 ||
6960                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6961                 piece == BlackLance && y == 1 ||
6962                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6963 }
6964
6965 void
6966 LeftClick (ClickType clickType, int xPix, int yPix)
6967 {
6968     int x, y;
6969     Boolean saveAnimate;
6970     static int second = 0, promotionChoice = 0, clearFlag = 0;
6971     char promoChoice = NULLCHAR;
6972     ChessSquare piece;
6973     static TimeMark lastClickTime, prevClickTime;
6974
6975     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
6976
6977     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
6978
6979     if (clickType == Press) ErrorPopDown();
6980
6981     x = EventToSquare(xPix, BOARD_WIDTH);
6982     y = EventToSquare(yPix, BOARD_HEIGHT);
6983     if (!flipView && y >= 0) {
6984         y = BOARD_HEIGHT - 1 - y;
6985     }
6986     if (flipView && x >= 0) {
6987         x = BOARD_WIDTH - 1 - x;
6988     }
6989
6990     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6991         defaultPromoChoice = promoSweep;
6992         promoSweep = EmptySquare;   // terminate sweep
6993         promoDefaultAltered = TRUE;
6994         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6995     }
6996
6997     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6998         if(clickType == Release) return; // ignore upclick of click-click destination
6999         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7000         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7001         if(gameInfo.holdingsWidth &&
7002                 (WhiteOnMove(currentMove)
7003                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7004                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7005             // click in right holdings, for determining promotion piece
7006             ChessSquare p = boards[currentMove][y][x];
7007             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7008             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7009             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7010                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7011                 fromX = fromY = -1;
7012                 return;
7013             }
7014         }
7015         DrawPosition(FALSE, boards[currentMove]);
7016         return;
7017     }
7018
7019     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7020     if(clickType == Press
7021             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7022               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7023               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7024         return;
7025
7026     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7027         // could be static click on premove from-square: abort premove
7028         gotPremove = 0;
7029         ClearPremoveHighlights();
7030     }
7031
7032     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7033         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7034
7035     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7036         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7037                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7038         defaultPromoChoice = DefaultPromoChoice(side);
7039     }
7040
7041     autoQueen = appData.alwaysPromoteToQueen;
7042
7043     if (fromX == -1) {
7044       int originalY = y;
7045       gatingPiece = EmptySquare;
7046       if (clickType != Press) {
7047         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7048             DragPieceEnd(xPix, yPix); dragging = 0;
7049             DrawPosition(FALSE, NULL);
7050         }
7051         return;
7052       }
7053       doubleClick = FALSE;
7054       fromX = x; fromY = y; toX = toY = -1;
7055       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7056          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7057          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7058             /* First square */
7059             if (OKToStartUserMove(fromX, fromY)) {
7060                 second = 0;
7061                 MarkTargetSquares(0);
7062                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7063                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7064                     promoSweep = defaultPromoChoice;
7065                     selectFlag = 0; lastX = xPix; lastY = yPix;
7066                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7067                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7068                 }
7069                 if (appData.highlightDragging) {
7070                     SetHighlights(fromX, fromY, -1, -1);
7071                 }
7072             } else fromX = fromY = -1;
7073             return;
7074         }
7075     }
7076
7077     /* fromX != -1 */
7078     if (clickType == Press && gameMode != EditPosition) {
7079         ChessSquare fromP;
7080         ChessSquare toP;
7081         int frc;
7082
7083         // ignore off-board to clicks
7084         if(y < 0 || x < 0) return;
7085
7086         /* Check if clicking again on the same color piece */
7087         fromP = boards[currentMove][fromY][fromX];
7088         toP = boards[currentMove][y][x];
7089         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7090         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7091              WhitePawn <= toP && toP <= WhiteKing &&
7092              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7093              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7094             (BlackPawn <= fromP && fromP <= BlackKing &&
7095              BlackPawn <= toP && toP <= BlackKing &&
7096              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7097              !(fromP == BlackKing && toP == BlackRook && frc))) {
7098             /* Clicked again on same color piece -- changed his mind */
7099             second = (x == fromX && y == fromY);
7100             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7101                 second = FALSE; // first double-click rather than scond click
7102                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7103             }
7104             promoDefaultAltered = FALSE;
7105             MarkTargetSquares(1);
7106            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7107             if (appData.highlightDragging) {
7108                 SetHighlights(x, y, -1, -1);
7109             } else {
7110                 ClearHighlights();
7111             }
7112             if (OKToStartUserMove(x, y)) {
7113                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7114                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7115                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7116                  gatingPiece = boards[currentMove][fromY][fromX];
7117                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7118                 fromX = x;
7119                 fromY = y; dragging = 1;
7120                 MarkTargetSquares(0);
7121                 DragPieceBegin(xPix, yPix, FALSE);
7122                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7123                     promoSweep = defaultPromoChoice;
7124                     selectFlag = 0; lastX = xPix; lastY = yPix;
7125                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7126                 }
7127             }
7128            }
7129            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7130            second = FALSE; 
7131         }
7132         // ignore clicks on holdings
7133         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7134     }
7135
7136     if (clickType == Release && x == fromX && y == fromY) {
7137         DragPieceEnd(xPix, yPix); dragging = 0;
7138         if(clearFlag) {
7139             // a deferred attempt to click-click move an empty square on top of a piece
7140             boards[currentMove][y][x] = EmptySquare;
7141             ClearHighlights();
7142             DrawPosition(FALSE, boards[currentMove]);
7143             fromX = fromY = -1; clearFlag = 0;
7144             return;
7145         }
7146         if (appData.animateDragging) {
7147             /* Undo animation damage if any */
7148             DrawPosition(FALSE, NULL);
7149         }
7150         if (second) {
7151             /* Second up/down in same square; just abort move */
7152             second = 0;
7153             fromX = fromY = -1;
7154             gatingPiece = EmptySquare;
7155             ClearHighlights();
7156             gotPremove = 0;
7157             ClearPremoveHighlights();
7158         } else {
7159             /* First upclick in same square; start click-click mode */
7160             SetHighlights(x, y, -1, -1);
7161         }
7162         return;
7163     }
7164
7165     clearFlag = 0;
7166
7167     /* we now have a different from- and (possibly off-board) to-square */
7168     /* Completed move */
7169     toX = x;
7170     toY = y;
7171     saveAnimate = appData.animate;
7172     MarkTargetSquares(1);
7173     if (clickType == Press) {
7174         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7175             // must be Edit Position mode with empty-square selected
7176             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7177             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7178             return;
7179         }
7180         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7181             ChessSquare piece = boards[currentMove][fromY][fromX];
7182             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7183             promoSweep = defaultPromoChoice;
7184             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7185             selectFlag = 0; lastX = xPix; lastY = yPix;
7186             Sweep(0); // Pawn that is going to promote: preview promotion piece
7187             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7188             DrawPosition(FALSE, boards[currentMove]);
7189             return;
7190         }
7191         /* Finish clickclick move */
7192         if (appData.animate || appData.highlightLastMove) {
7193             SetHighlights(fromX, fromY, toX, toY);
7194         } else {
7195             ClearHighlights();
7196         }
7197     } else {
7198         /* Finish drag move */
7199         if (appData.highlightLastMove) {
7200             SetHighlights(fromX, fromY, toX, toY);
7201         } else {
7202             ClearHighlights();
7203         }
7204         DragPieceEnd(xPix, yPix); dragging = 0;
7205         /* Don't animate move and drag both */
7206         appData.animate = FALSE;
7207     }
7208
7209     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7210     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7211         ChessSquare piece = boards[currentMove][fromY][fromX];
7212         if(gameMode == EditPosition && piece != EmptySquare &&
7213            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7214             int n;
7215
7216             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7217                 n = PieceToNumber(piece - (int)BlackPawn);
7218                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7219                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7220                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7221             } else
7222             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7223                 n = PieceToNumber(piece);
7224                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7225                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7226                 boards[currentMove][n][BOARD_WIDTH-2]++;
7227             }
7228             boards[currentMove][fromY][fromX] = EmptySquare;
7229         }
7230         ClearHighlights();
7231         fromX = fromY = -1;
7232         DrawPosition(TRUE, boards[currentMove]);
7233         return;
7234     }
7235
7236     // off-board moves should not be highlighted
7237     if(x < 0 || y < 0) ClearHighlights();
7238
7239     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7240
7241     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7242         SetHighlights(fromX, fromY, toX, toY);
7243         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7244             // [HGM] super: promotion to captured piece selected from holdings
7245             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7246             promotionChoice = TRUE;
7247             // kludge follows to temporarily execute move on display, without promoting yet
7248             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7249             boards[currentMove][toY][toX] = p;
7250             DrawPosition(FALSE, boards[currentMove]);
7251             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7252             boards[currentMove][toY][toX] = q;
7253             DisplayMessage("Click in holdings to choose piece", "");
7254             return;
7255         }
7256         PromotionPopUp();
7257     } else {
7258         int oldMove = currentMove;
7259         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7260         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7261         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7262         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7263            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7264             DrawPosition(TRUE, boards[currentMove]);
7265         fromX = fromY = -1;
7266     }
7267     appData.animate = saveAnimate;
7268     if (appData.animate || appData.animateDragging) {
7269         /* Undo animation damage if needed */
7270         DrawPosition(FALSE, NULL);
7271     }
7272 }
7273
7274 int
7275 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7276 {   // front-end-free part taken out of PieceMenuPopup
7277     int whichMenu; int xSqr, ySqr;
7278
7279     if(seekGraphUp) { // [HGM] seekgraph
7280         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7281         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7282         return -2;
7283     }
7284
7285     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7286          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7287         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7288         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7289         if(action == Press)   {
7290             originalFlip = flipView;
7291             flipView = !flipView; // temporarily flip board to see game from partners perspective
7292             DrawPosition(TRUE, partnerBoard);
7293             DisplayMessage(partnerStatus, "");
7294             partnerUp = TRUE;
7295         } else if(action == Release) {
7296             flipView = originalFlip;
7297             DrawPosition(TRUE, boards[currentMove]);
7298             partnerUp = FALSE;
7299         }
7300         return -2;
7301     }
7302
7303     xSqr = EventToSquare(x, BOARD_WIDTH);
7304     ySqr = EventToSquare(y, BOARD_HEIGHT);
7305     if (action == Release) {
7306         if(pieceSweep != EmptySquare) {
7307             EditPositionMenuEvent(pieceSweep, toX, toY);
7308             pieceSweep = EmptySquare;
7309         } else UnLoadPV(); // [HGM] pv
7310     }
7311     if (action != Press) return -2; // return code to be ignored
7312     switch (gameMode) {
7313       case IcsExamining:
7314         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7315       case EditPosition:
7316         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7317         if (xSqr < 0 || ySqr < 0) return -1;
7318         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7319         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7320         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7321         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7322         NextPiece(0);
7323         return 2; // grab
7324       case IcsObserving:
7325         if(!appData.icsEngineAnalyze) return -1;
7326       case IcsPlayingWhite:
7327       case IcsPlayingBlack:
7328         if(!appData.zippyPlay) goto noZip;
7329       case AnalyzeMode:
7330       case AnalyzeFile:
7331       case MachinePlaysWhite:
7332       case MachinePlaysBlack:
7333       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7334         if (!appData.dropMenu) {
7335           LoadPV(x, y);
7336           return 2; // flag front-end to grab mouse events
7337         }
7338         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7339            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7340       case EditGame:
7341       noZip:
7342         if (xSqr < 0 || ySqr < 0) return -1;
7343         if (!appData.dropMenu || appData.testLegality &&
7344             gameInfo.variant != VariantBughouse &&
7345             gameInfo.variant != VariantCrazyhouse) return -1;
7346         whichMenu = 1; // drop menu
7347         break;
7348       default:
7349         return -1;
7350     }
7351
7352     if (((*fromX = xSqr) < 0) ||
7353         ((*fromY = ySqr) < 0)) {
7354         *fromX = *fromY = -1;
7355         return -1;
7356     }
7357     if (flipView)
7358       *fromX = BOARD_WIDTH - 1 - *fromX;
7359     else
7360       *fromY = BOARD_HEIGHT - 1 - *fromY;
7361
7362     return whichMenu;
7363 }
7364
7365 void
7366 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7367 {
7368 //    char * hint = lastHint;
7369     FrontEndProgramStats stats;
7370
7371     stats.which = cps == &first ? 0 : 1;
7372     stats.depth = cpstats->depth;
7373     stats.nodes = cpstats->nodes;
7374     stats.score = cpstats->score;
7375     stats.time = cpstats->time;
7376     stats.pv = cpstats->movelist;
7377     stats.hint = lastHint;
7378     stats.an_move_index = 0;
7379     stats.an_move_count = 0;
7380
7381     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7382         stats.hint = cpstats->move_name;
7383         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7384         stats.an_move_count = cpstats->nr_moves;
7385     }
7386
7387     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
7388
7389     SetProgramStats( &stats );
7390 }
7391
7392 void
7393 ClearEngineOutputPane (int which)
7394 {
7395     static FrontEndProgramStats dummyStats;
7396     dummyStats.which = which;
7397     dummyStats.pv = "#";
7398     SetProgramStats( &dummyStats );
7399 }
7400
7401 #define MAXPLAYERS 500
7402
7403 char *
7404 TourneyStandings (int display)
7405 {
7406     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7407     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7408     char result, *p, *names[MAXPLAYERS];
7409
7410     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7411         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7412     names[0] = p = strdup(appData.participants);
7413     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7414
7415     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7416
7417     while(result = appData.results[nr]) {
7418         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7419         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7420         wScore = bScore = 0;
7421         switch(result) {
7422           case '+': wScore = 2; break;
7423           case '-': bScore = 2; break;
7424           case '=': wScore = bScore = 1; break;
7425           case ' ':
7426           case '*': return strdup("busy"); // tourney not finished
7427         }
7428         score[w] += wScore;
7429         score[b] += bScore;
7430         games[w]++;
7431         games[b]++;
7432         nr++;
7433     }
7434     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7435     for(w=0; w<nPlayers; w++) {
7436         bScore = -1;
7437         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7438         ranking[w] = b; points[w] = bScore; score[b] = -2;
7439     }
7440     p = malloc(nPlayers*34+1);
7441     for(w=0; w<nPlayers && w<display; w++)
7442         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7443     free(names[0]);
7444     return p;
7445 }
7446
7447 void
7448 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7449 {       // count all piece types
7450         int p, f, r;
7451         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7452         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7453         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7454                 p = board[r][f];
7455                 pCnt[p]++;
7456                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7457                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7458                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7459                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7460                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7461                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7462         }
7463 }
7464
7465 int
7466 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7467 {
7468         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7469         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7470
7471         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7472         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7473         if(myPawns == 2 && nMine == 3) // KPP
7474             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7475         if(myPawns == 1 && nMine == 2) // KP
7476             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7477         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7478             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7479         if(myPawns) return FALSE;
7480         if(pCnt[WhiteRook+side])
7481             return pCnt[BlackRook-side] ||
7482                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7483                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7484                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7485         if(pCnt[WhiteCannon+side]) {
7486             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7487             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7488         }
7489         if(pCnt[WhiteKnight+side])
7490             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7491         return FALSE;
7492 }
7493
7494 int
7495 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7496 {
7497         VariantClass v = gameInfo.variant;
7498
7499         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7500         if(v == VariantShatranj) return TRUE; // always winnable through baring
7501         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7502         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7503
7504         if(v == VariantXiangqi) {
7505                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7506
7507                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7508                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7509                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7510                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7511                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7512                 if(stale) // we have at least one last-rank P plus perhaps C
7513                     return majors // KPKX
7514                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7515                 else // KCA*E*
7516                     return pCnt[WhiteFerz+side] // KCAK
7517                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7518                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7519                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7520
7521         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7522                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7523
7524                 if(nMine == 1) return FALSE; // bare King
7525                 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
7526                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7527                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7528                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7529                 if(pCnt[WhiteKnight+side])
7530                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7531                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7532                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7533                 if(nBishops)
7534                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7535                 if(pCnt[WhiteAlfil+side])
7536                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7537                 if(pCnt[WhiteWazir+side])
7538                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7539         }
7540
7541         return TRUE;
7542 }
7543
7544 int
7545 CompareWithRights (Board b1, Board b2)
7546 {
7547     int rights = 0;
7548     if(!CompareBoards(b1, b2)) return FALSE;
7549     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7550     /* compare castling rights */
7551     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7552            rights++; /* King lost rights, while rook still had them */
7553     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7554         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7555            rights++; /* but at least one rook lost them */
7556     }
7557     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7558            rights++;
7559     if( b1[CASTLING][5] != NoRights ) {
7560         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7561            rights++;
7562     }
7563     return rights == 0;
7564 }
7565
7566 int
7567 Adjudicate (ChessProgramState *cps)
7568 {       // [HGM] some adjudications useful with buggy engines
7569         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7570         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7571         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7572         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7573         int k, count = 0; static int bare = 1;
7574         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7575         Boolean canAdjudicate = !appData.icsActive;
7576
7577         // most tests only when we understand the game, i.e. legality-checking on
7578             if( appData.testLegality )
7579             {   /* [HGM] Some more adjudications for obstinate engines */
7580                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7581                 static int moveCount = 6;
7582                 ChessMove result;
7583                 char *reason = NULL;
7584
7585                 /* Count what is on board. */
7586                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7587
7588                 /* Some material-based adjudications that have to be made before stalemate test */
7589                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7590                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7591                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7592                      if(canAdjudicate && appData.checkMates) {
7593                          if(engineOpponent)
7594                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7595                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7596                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7597                          return 1;
7598                      }
7599                 }
7600
7601                 /* Bare King in Shatranj (loses) or Losers (wins) */
7602                 if( nrW == 1 || nrB == 1) {
7603                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7604                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7605                      if(canAdjudicate && appData.checkMates) {
7606                          if(engineOpponent)
7607                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7608                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7609                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7610                          return 1;
7611                      }
7612                   } else
7613                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7614                   {    /* bare King */
7615                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7616                         if(canAdjudicate && appData.checkMates) {
7617                             /* but only adjudicate if adjudication enabled */
7618                             if(engineOpponent)
7619                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7620                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7621                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7622                             return 1;
7623                         }
7624                   }
7625                 } else bare = 1;
7626
7627
7628             // don't wait for engine to announce game end if we can judge ourselves
7629             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7630               case MT_CHECK:
7631                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7632                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7633                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7634                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7635                             checkCnt++;
7636                         if(checkCnt >= 2) {
7637                             reason = "Xboard adjudication: 3rd check";
7638                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7639                             break;
7640                         }
7641                     }
7642                 }
7643               case MT_NONE:
7644               default:
7645                 break;
7646               case MT_STALEMATE:
7647               case MT_STAINMATE:
7648                 reason = "Xboard adjudication: Stalemate";
7649                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7650                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7651                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7652                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7653                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7654                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7655                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7656                                                                         EP_CHECKMATE : EP_WINS);
7657                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7658                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7659                 }
7660                 break;
7661               case MT_CHECKMATE:
7662                 reason = "Xboard adjudication: Checkmate";
7663                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7664                 break;
7665             }
7666
7667                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7668                     case EP_STALEMATE:
7669                         result = GameIsDrawn; break;
7670                     case EP_CHECKMATE:
7671                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7672                     case EP_WINS:
7673                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7674                     default:
7675                         result = EndOfFile;
7676                 }
7677                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7678                     if(engineOpponent)
7679                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7680                     GameEnds( result, reason, GE_XBOARD );
7681                     return 1;
7682                 }
7683
7684                 /* Next absolutely insufficient mating material. */
7685                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7686                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7687                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7688
7689                      /* always flag draws, for judging claims */
7690                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7691
7692                      if(canAdjudicate && appData.materialDraws) {
7693                          /* but only adjudicate them if adjudication enabled */
7694                          if(engineOpponent) {
7695                            SendToProgram("force\n", engineOpponent); // suppress reply
7696                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7697                          }
7698                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7699                          return 1;
7700                      }
7701                 }
7702
7703                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7704                 if(gameInfo.variant == VariantXiangqi ?
7705                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7706                  : nrW + nrB == 4 &&
7707                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7708                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7709                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7710                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7711                    ) ) {
7712                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7713                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7714                           if(engineOpponent) {
7715                             SendToProgram("force\n", engineOpponent); // suppress reply
7716                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7717                           }
7718                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7719                           return 1;
7720                      }
7721                 } else moveCount = 6;
7722             }
7723
7724         // Repetition draws and 50-move rule can be applied independently of legality testing
7725
7726                 /* Check for rep-draws */
7727                 count = 0;
7728                 for(k = forwardMostMove-2;
7729                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7730                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7731                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7732                     k-=2)
7733                 {   int rights=0;
7734                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7735                         /* compare castling rights */
7736                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7737                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7738                                 rights++; /* King lost rights, while rook still had them */
7739                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7740                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7741                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7742                                    rights++; /* but at least one rook lost them */
7743                         }
7744                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7745                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7746                                 rights++;
7747                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7748                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7749                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7750                                    rights++;
7751                         }
7752                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7753                             && appData.drawRepeats > 1) {
7754                              /* adjudicate after user-specified nr of repeats */
7755                              int result = GameIsDrawn;
7756                              char *details = "XBoard adjudication: repetition draw";
7757                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7758                                 // [HGM] xiangqi: check for forbidden perpetuals
7759                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7760                                 for(m=forwardMostMove; m>k; m-=2) {
7761                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7762                                         ourPerpetual = 0; // the current mover did not always check
7763                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7764                                         hisPerpetual = 0; // the opponent did not always check
7765                                 }
7766                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7767                                                                         ourPerpetual, hisPerpetual);
7768                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7769                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7770                                     details = "Xboard adjudication: perpetual checking";
7771                                 } else
7772                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7773                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7774                                 } else
7775                                 // Now check for perpetual chases
7776                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7777                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7778                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7779                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7780                                         static char resdet[MSG_SIZ];
7781                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7782                                         details = resdet;
7783                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7784                                     } else
7785                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7786                                         break; // Abort repetition-checking loop.
7787                                 }
7788                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7789                              }
7790                              if(engineOpponent) {
7791                                SendToProgram("force\n", engineOpponent); // suppress reply
7792                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7793                              }
7794                              GameEnds( result, details, GE_XBOARD );
7795                              return 1;
7796                         }
7797                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7798                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7799                     }
7800                 }
7801
7802                 /* Now we test for 50-move draws. Determine ply count */
7803                 count = forwardMostMove;
7804                 /* look for last irreversble move */
7805                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7806                     count--;
7807                 /* if we hit starting position, add initial plies */
7808                 if( count == backwardMostMove )
7809                     count -= initialRulePlies;
7810                 count = forwardMostMove - count;
7811                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7812                         // adjust reversible move counter for checks in Xiangqi
7813                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7814                         if(i < backwardMostMove) i = backwardMostMove;
7815                         while(i <= forwardMostMove) {
7816                                 lastCheck = inCheck; // check evasion does not count
7817                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7818                                 if(inCheck || lastCheck) count--; // check does not count
7819                                 i++;
7820                         }
7821                 }
7822                 if( count >= 100)
7823                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7824                          /* this is used to judge if draw claims are legal */
7825                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7826                          if(engineOpponent) {
7827                            SendToProgram("force\n", engineOpponent); // suppress reply
7828                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7829                          }
7830                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7831                          return 1;
7832                 }
7833
7834                 /* if draw offer is pending, treat it as a draw claim
7835                  * when draw condition present, to allow engines a way to
7836                  * claim draws before making their move to avoid a race
7837                  * condition occurring after their move
7838                  */
7839                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7840                          char *p = NULL;
7841                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7842                              p = "Draw claim: 50-move rule";
7843                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7844                              p = "Draw claim: 3-fold repetition";
7845                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7846                              p = "Draw claim: insufficient mating material";
7847                          if( p != NULL && canAdjudicate) {
7848                              if(engineOpponent) {
7849                                SendToProgram("force\n", engineOpponent); // suppress reply
7850                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7851                              }
7852                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7853                              return 1;
7854                          }
7855                 }
7856
7857                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7858                     if(engineOpponent) {
7859                       SendToProgram("force\n", engineOpponent); // suppress reply
7860                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7861                     }
7862                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7863                     return 1;
7864                 }
7865         return 0;
7866 }
7867
7868 char *
7869 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7870 {   // [HGM] book: this routine intercepts moves to simulate book replies
7871     char *bookHit = NULL;
7872
7873     //first determine if the incoming move brings opponent into his book
7874     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7875         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7876     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7877     if(bookHit != NULL && !cps->bookSuspend) {
7878         // make sure opponent is not going to reply after receiving move to book position
7879         SendToProgram("force\n", cps);
7880         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7881     }
7882     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7883     // now arrange restart after book miss
7884     if(bookHit) {
7885         // after a book hit we never send 'go', and the code after the call to this routine
7886         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7887         char buf[MSG_SIZ], *move = bookHit;
7888         if(cps->useSAN) {
7889             int fromX, fromY, toX, toY;
7890             char promoChar;
7891             ChessMove moveType;
7892             move = buf + 30;
7893             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7894                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7895                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7896                                     PosFlags(forwardMostMove),
7897                                     fromY, fromX, toY, toX, promoChar, move);
7898             } else {
7899                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7900                 bookHit = NULL;
7901             }
7902         }
7903         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7904         SendToProgram(buf, cps);
7905         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7906     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7907         SendToProgram("go\n", cps);
7908         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7909     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7910         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7911             SendToProgram("go\n", cps);
7912         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7913     }
7914     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7915 }
7916
7917 char *savedMessage;
7918 ChessProgramState *savedState;
7919 void
7920 DeferredBookMove (void)
7921 {
7922         if(savedState->lastPing != savedState->lastPong)
7923                     ScheduleDelayedEvent(DeferredBookMove, 10);
7924         else
7925         HandleMachineMove(savedMessage, savedState);
7926 }
7927
7928 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7929
7930 void
7931 HandleMachineMove (char *message, ChessProgramState *cps)
7932 {
7933     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7934     char realname[MSG_SIZ];
7935     int fromX, fromY, toX, toY;
7936     ChessMove moveType;
7937     char promoChar;
7938     char *p, *pv=buf1;
7939     int machineWhite;
7940     char *bookHit;
7941
7942     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7943         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7944         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7945             DisplayError(_("Invalid pairing from pairing engine"), 0);
7946             return;
7947         }
7948         pairingReceived = 1;
7949         NextMatchGame();
7950         return; // Skim the pairing messages here.
7951     }
7952
7953     cps->userError = 0;
7954
7955 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7956     /*
7957      * Kludge to ignore BEL characters
7958      */
7959     while (*message == '\007') message++;
7960
7961     /*
7962      * [HGM] engine debug message: ignore lines starting with '#' character
7963      */
7964     if(cps->debug && *message == '#') return;
7965
7966     /*
7967      * Look for book output
7968      */
7969     if (cps == &first && bookRequested) {
7970         if (message[0] == '\t' || message[0] == ' ') {
7971             /* Part of the book output is here; append it */
7972             strcat(bookOutput, message);
7973             strcat(bookOutput, "  \n");
7974             return;
7975         } else if (bookOutput[0] != NULLCHAR) {
7976             /* All of book output has arrived; display it */
7977             char *p = bookOutput;
7978             while (*p != NULLCHAR) {
7979                 if (*p == '\t') *p = ' ';
7980                 p++;
7981             }
7982             DisplayInformation(bookOutput);
7983             bookRequested = FALSE;
7984             /* Fall through to parse the current output */
7985         }
7986     }
7987
7988     /*
7989      * Look for machine move.
7990      */
7991     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7992         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7993     {
7994         /* This method is only useful on engines that support ping */
7995         if (cps->lastPing != cps->lastPong) {
7996           if (gameMode == BeginningOfGame) {
7997             /* Extra move from before last new; ignore */
7998             if (appData.debugMode) {
7999                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8000             }
8001           } else {
8002             if (appData.debugMode) {
8003                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8004                         cps->which, gameMode);
8005             }
8006
8007             SendToProgram("undo\n", cps);
8008           }
8009           return;
8010         }
8011
8012         switch (gameMode) {
8013           case BeginningOfGame:
8014             /* Extra move from before last reset; ignore */
8015             if (appData.debugMode) {
8016                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8017             }
8018             return;
8019
8020           case EndOfGame:
8021           case IcsIdle:
8022           default:
8023             /* Extra move after we tried to stop.  The mode test is
8024                not a reliable way of detecting this problem, but it's
8025                the best we can do on engines that don't support ping.
8026             */
8027             if (appData.debugMode) {
8028                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8029                         cps->which, gameMode);
8030             }
8031             SendToProgram("undo\n", cps);
8032             return;
8033
8034           case MachinePlaysWhite:
8035           case IcsPlayingWhite:
8036             machineWhite = TRUE;
8037             break;
8038
8039           case MachinePlaysBlack:
8040           case IcsPlayingBlack:
8041             machineWhite = FALSE;
8042             break;
8043
8044           case TwoMachinesPlay:
8045             machineWhite = (cps->twoMachinesColor[0] == 'w');
8046             break;
8047         }
8048         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8049             if (appData.debugMode) {
8050                 fprintf(debugFP,
8051                         "Ignoring move out of turn by %s, gameMode %d"
8052                         ", forwardMost %d\n",
8053                         cps->which, gameMode, forwardMostMove);
8054             }
8055             return;
8056         }
8057
8058         if(cps->alphaRank) AlphaRank(machineMove, 4);
8059         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8060                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8061             /* Machine move could not be parsed; ignore it. */
8062           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8063                     machineMove, _(cps->which));
8064             DisplayError(buf1, 0);
8065             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8066                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8067             if (gameMode == TwoMachinesPlay) {
8068               GameEnds(machineWhite ? BlackWins : WhiteWins,
8069                        buf1, GE_XBOARD);
8070             }
8071             return;
8072         }
8073
8074         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8075         /* So we have to redo legality test with true e.p. status here,  */
8076         /* to make sure an illegal e.p. capture does not slip through,   */
8077         /* to cause a forfeit on a justified illegal-move complaint      */
8078         /* of the opponent.                                              */
8079         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8080            ChessMove moveType;
8081            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8082                              fromY, fromX, toY, toX, promoChar);
8083             if(moveType == IllegalMove) {
8084               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8085                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8086                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8087                            buf1, GE_XBOARD);
8088                 return;
8089            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8090            /* [HGM] Kludge to handle engines that send FRC-style castling
8091               when they shouldn't (like TSCP-Gothic) */
8092            switch(moveType) {
8093              case WhiteASideCastleFR:
8094              case BlackASideCastleFR:
8095                toX+=2;
8096                currentMoveString[2]++;
8097                break;
8098              case WhiteHSideCastleFR:
8099              case BlackHSideCastleFR:
8100                toX--;
8101                currentMoveString[2]--;
8102                break;
8103              default: ; // nothing to do, but suppresses warning of pedantic compilers
8104            }
8105         }
8106         hintRequested = FALSE;
8107         lastHint[0] = NULLCHAR;
8108         bookRequested = FALSE;
8109         /* Program may be pondering now */
8110         cps->maybeThinking = TRUE;
8111         if (cps->sendTime == 2) cps->sendTime = 1;
8112         if (cps->offeredDraw) cps->offeredDraw--;
8113
8114         /* [AS] Save move info*/
8115         pvInfoList[ forwardMostMove ].score = programStats.score;
8116         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8117         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8118
8119         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8120
8121         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8122         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8123             int count = 0;
8124
8125             while( count < adjudicateLossPlies ) {
8126                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8127
8128                 if( count & 1 ) {
8129                     score = -score; /* Flip score for winning side */
8130                 }
8131
8132                 if( score > adjudicateLossThreshold ) {
8133                     break;
8134                 }
8135
8136                 count++;
8137             }
8138
8139             if( count >= adjudicateLossPlies ) {
8140                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8141
8142                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8143                     "Xboard adjudication",
8144                     GE_XBOARD );
8145
8146                 return;
8147             }
8148         }
8149
8150         if(Adjudicate(cps)) {
8151             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8152             return; // [HGM] adjudicate: for all automatic game ends
8153         }
8154
8155 #if ZIPPY
8156         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8157             first.initDone) {
8158           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8159                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8160                 SendToICS("draw ");
8161                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8162           }
8163           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8164           ics_user_moved = 1;
8165           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8166                 char buf[3*MSG_SIZ];
8167
8168                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8169                         programStats.score / 100.,
8170                         programStats.depth,
8171                         programStats.time / 100.,
8172                         (unsigned int)programStats.nodes,
8173                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8174                         programStats.movelist);
8175                 SendToICS(buf);
8176 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8177           }
8178         }
8179 #endif
8180
8181         /* [AS] Clear stats for next move */
8182         ClearProgramStats();
8183         thinkOutput[0] = NULLCHAR;
8184         hiddenThinkOutputState = 0;
8185
8186         bookHit = NULL;
8187         if (gameMode == TwoMachinesPlay) {
8188             /* [HGM] relaying draw offers moved to after reception of move */
8189             /* and interpreting offer as claim if it brings draw condition */
8190             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8191                 SendToProgram("draw\n", cps->other);
8192             }
8193             if (cps->other->sendTime) {
8194                 SendTimeRemaining(cps->other,
8195                                   cps->other->twoMachinesColor[0] == 'w');
8196             }
8197             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8198             if (firstMove && !bookHit) {
8199                 firstMove = FALSE;
8200                 if (cps->other->useColors) {
8201                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8202                 }
8203                 SendToProgram("go\n", cps->other);
8204             }
8205             cps->other->maybeThinking = TRUE;
8206         }
8207
8208         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8209
8210         if (!pausing && appData.ringBellAfterMoves) {
8211             RingBell();
8212         }
8213
8214         /*
8215          * Reenable menu items that were disabled while
8216          * machine was thinking
8217          */
8218         if (gameMode != TwoMachinesPlay)
8219             SetUserThinkingEnables();
8220
8221         // [HGM] book: after book hit opponent has received move and is now in force mode
8222         // force the book reply into it, and then fake that it outputted this move by jumping
8223         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8224         if(bookHit) {
8225                 static char bookMove[MSG_SIZ]; // a bit generous?
8226
8227                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8228                 strcat(bookMove, bookHit);
8229                 message = bookMove;
8230                 cps = cps->other;
8231                 programStats.nodes = programStats.depth = programStats.time =
8232                 programStats.score = programStats.got_only_move = 0;
8233                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8234
8235                 if(cps->lastPing != cps->lastPong) {
8236                     savedMessage = message; // args for deferred call
8237                     savedState = cps;
8238                     ScheduleDelayedEvent(DeferredBookMove, 10);
8239                     return;
8240                 }
8241                 goto FakeBookMove;
8242         }
8243
8244         return;
8245     }
8246
8247     /* Set special modes for chess engines.  Later something general
8248      *  could be added here; for now there is just one kludge feature,
8249      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8250      *  when "xboard" is given as an interactive command.
8251      */
8252     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8253         cps->useSigint = FALSE;
8254         cps->useSigterm = FALSE;
8255     }
8256     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8257       ParseFeatures(message+8, cps);
8258       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8259     }
8260
8261     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8262                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8263       int dummy, s=6; char buf[MSG_SIZ];
8264       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8265       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8266       if(startedFromSetupPosition) return;
8267       ParseFEN(boards[0], &dummy, message+s);
8268       DrawPosition(TRUE, boards[0]);
8269       startedFromSetupPosition = TRUE;
8270       return;
8271     }
8272     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8273      * want this, I was asked to put it in, and obliged.
8274      */
8275     if (!strncmp(message, "setboard ", 9)) {
8276         Board initial_position;
8277
8278         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8279
8280         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8281             DisplayError(_("Bad FEN received from engine"), 0);
8282             return ;
8283         } else {
8284            Reset(TRUE, FALSE);
8285            CopyBoard(boards[0], initial_position);
8286            initialRulePlies = FENrulePlies;
8287            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8288            else gameMode = MachinePlaysBlack;
8289            DrawPosition(FALSE, boards[currentMove]);
8290         }
8291         return;
8292     }
8293
8294     /*
8295      * Look for communication commands
8296      */
8297     if (!strncmp(message, "telluser ", 9)) {
8298         if(message[9] == '\\' && message[10] == '\\')
8299             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8300         PlayTellSound();
8301         DisplayNote(message + 9);
8302         return;
8303     }
8304     if (!strncmp(message, "tellusererror ", 14)) {
8305         cps->userError = 1;
8306         if(message[14] == '\\' && message[15] == '\\')
8307             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8308         PlayTellSound();
8309         DisplayError(message + 14, 0);
8310         return;
8311     }
8312     if (!strncmp(message, "tellopponent ", 13)) {
8313       if (appData.icsActive) {
8314         if (loggedOn) {
8315           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8316           SendToICS(buf1);
8317         }
8318       } else {
8319         DisplayNote(message + 13);
8320       }
8321       return;
8322     }
8323     if (!strncmp(message, "tellothers ", 11)) {
8324       if (appData.icsActive) {
8325         if (loggedOn) {
8326           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8327           SendToICS(buf1);
8328         }
8329       }
8330       return;
8331     }
8332     if (!strncmp(message, "tellall ", 8)) {
8333       if (appData.icsActive) {
8334         if (loggedOn) {
8335           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8336           SendToICS(buf1);
8337         }
8338       } else {
8339         DisplayNote(message + 8);
8340       }
8341       return;
8342     }
8343     if (strncmp(message, "warning", 7) == 0) {
8344         /* Undocumented feature, use tellusererror in new code */
8345         DisplayError(message, 0);
8346         return;
8347     }
8348     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8349         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8350         strcat(realname, " query");
8351         AskQuestion(realname, buf2, buf1, cps->pr);
8352         return;
8353     }
8354     /* Commands from the engine directly to ICS.  We don't allow these to be
8355      *  sent until we are logged on. Crafty kibitzes have been known to
8356      *  interfere with the login process.
8357      */
8358     if (loggedOn) {
8359         if (!strncmp(message, "tellics ", 8)) {
8360             SendToICS(message + 8);
8361             SendToICS("\n");
8362             return;
8363         }
8364         if (!strncmp(message, "tellicsnoalias ", 15)) {
8365             SendToICS(ics_prefix);
8366             SendToICS(message + 15);
8367             SendToICS("\n");
8368             return;
8369         }
8370         /* The following are for backward compatibility only */
8371         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8372             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8373             SendToICS(ics_prefix);
8374             SendToICS(message);
8375             SendToICS("\n");
8376             return;
8377         }
8378     }
8379     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8380         return;
8381     }
8382     /*
8383      * If the move is illegal, cancel it and redraw the board.
8384      * Also deal with other error cases.  Matching is rather loose
8385      * here to accommodate engines written before the spec.
8386      */
8387     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8388         strncmp(message, "Error", 5) == 0) {
8389         if (StrStr(message, "name") ||
8390             StrStr(message, "rating") || StrStr(message, "?") ||
8391             StrStr(message, "result") || StrStr(message, "board") ||
8392             StrStr(message, "bk") || StrStr(message, "computer") ||
8393             StrStr(message, "variant") || StrStr(message, "hint") ||
8394             StrStr(message, "random") || StrStr(message, "depth") ||
8395             StrStr(message, "accepted")) {
8396             return;
8397         }
8398         if (StrStr(message, "protover")) {
8399           /* Program is responding to input, so it's apparently done
8400              initializing, and this error message indicates it is
8401              protocol version 1.  So we don't need to wait any longer
8402              for it to initialize and send feature commands. */
8403           FeatureDone(cps, 1);
8404           cps->protocolVersion = 1;
8405           return;
8406         }
8407         cps->maybeThinking = FALSE;
8408
8409         if (StrStr(message, "draw")) {
8410             /* Program doesn't have "draw" command */
8411             cps->sendDrawOffers = 0;
8412             return;
8413         }
8414         if (cps->sendTime != 1 &&
8415             (StrStr(message, "time") || StrStr(message, "otim"))) {
8416           /* Program apparently doesn't have "time" or "otim" command */
8417           cps->sendTime = 0;
8418           return;
8419         }
8420         if (StrStr(message, "analyze")) {
8421             cps->analysisSupport = FALSE;
8422             cps->analyzing = FALSE;
8423 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8424             EditGameEvent(); // [HGM] try to preserve loaded game
8425             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8426             DisplayError(buf2, 0);
8427             return;
8428         }
8429         if (StrStr(message, "(no matching move)st")) {
8430           /* Special kludge for GNU Chess 4 only */
8431           cps->stKludge = TRUE;
8432           SendTimeControl(cps, movesPerSession, timeControl,
8433                           timeIncrement, appData.searchDepth,
8434                           searchTime);
8435           return;
8436         }
8437         if (StrStr(message, "(no matching move)sd")) {
8438           /* Special kludge for GNU Chess 4 only */
8439           cps->sdKludge = TRUE;
8440           SendTimeControl(cps, movesPerSession, timeControl,
8441                           timeIncrement, appData.searchDepth,
8442                           searchTime);
8443           return;
8444         }
8445         if (!StrStr(message, "llegal")) {
8446             return;
8447         }
8448         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8449             gameMode == IcsIdle) return;
8450         if (forwardMostMove <= backwardMostMove) return;
8451         if (pausing) PauseEvent();
8452       if(appData.forceIllegal) {
8453             // [HGM] illegal: machine refused move; force position after move into it
8454           SendToProgram("force\n", cps);
8455           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8456                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8457                 // when black is to move, while there might be nothing on a2 or black
8458                 // might already have the move. So send the board as if white has the move.
8459                 // But first we must change the stm of the engine, as it refused the last move
8460                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8461                 if(WhiteOnMove(forwardMostMove)) {
8462                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8463                     SendBoard(cps, forwardMostMove); // kludgeless board
8464                 } else {
8465                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8466                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8467                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8468                 }
8469           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8470             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8471                  gameMode == TwoMachinesPlay)
8472               SendToProgram("go\n", cps);
8473             return;
8474       } else
8475         if (gameMode == PlayFromGameFile) {
8476             /* Stop reading this game file */
8477             gameMode = EditGame;
8478             ModeHighlight();
8479         }
8480         /* [HGM] illegal-move claim should forfeit game when Xboard */
8481         /* only passes fully legal moves                            */
8482         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8483             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8484                                 "False illegal-move claim", GE_XBOARD );
8485             return; // do not take back move we tested as valid
8486         }
8487         currentMove = forwardMostMove-1;
8488         DisplayMove(currentMove-1); /* before DisplayMoveError */
8489         SwitchClocks(forwardMostMove-1); // [HGM] race
8490         DisplayBothClocks();
8491         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8492                 parseList[currentMove], _(cps->which));
8493         DisplayMoveError(buf1);
8494         DrawPosition(FALSE, boards[currentMove]);
8495
8496         SetUserThinkingEnables();
8497         return;
8498     }
8499     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8500         /* Program has a broken "time" command that
8501            outputs a string not ending in newline.
8502            Don't use it. */
8503         cps->sendTime = 0;
8504     }
8505
8506     /*
8507      * If chess program startup fails, exit with an error message.
8508      * Attempts to recover here are futile. [HGM] Well, we try anyway
8509      */
8510     if ((StrStr(message, "unknown host") != NULL)
8511         || (StrStr(message, "No remote directory") != NULL)
8512         || (StrStr(message, "not found") != NULL)
8513         || (StrStr(message, "No such file") != NULL)
8514         || (StrStr(message, "can't alloc") != NULL)
8515         || (StrStr(message, "Permission denied") != NULL)) {
8516
8517         cps->maybeThinking = FALSE;
8518         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8519                 _(cps->which), cps->program, cps->host, message);
8520         RemoveInputSource(cps->isr);
8521         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8522             cps->isr = NULL;
8523             DestroyChildProcess(cps->pr, 9 ); // just to be sure
8524             cps->pr = NoProc; 
8525             if(cps == &first) {
8526                 appData.noChessProgram = TRUE;
8527                 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8528                 gameMode = BeginningOfGame; ModeHighlight();
8529                 SetNCPMode();
8530             }
8531             if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8532             DisplayMessage("", ""); // erase waiting message
8533             DisplayError(buf1, 0);
8534         }
8535         return;
8536     }
8537
8538     /*
8539      * Look for hint output
8540      */
8541     if (sscanf(message, "Hint: %s", buf1) == 1) {
8542         if (cps == &first && hintRequested) {
8543             hintRequested = FALSE;
8544             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8545                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8546                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8547                                     PosFlags(forwardMostMove),
8548                                     fromY, fromX, toY, toX, promoChar, buf1);
8549                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8550                 DisplayInformation(buf2);
8551             } else {
8552                 /* Hint move could not be parsed!? */
8553               snprintf(buf2, sizeof(buf2),
8554                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8555                         buf1, _(cps->which));
8556                 DisplayError(buf2, 0);
8557             }
8558         } else {
8559           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8560         }
8561         return;
8562     }
8563
8564     /*
8565      * Ignore other messages if game is not in progress
8566      */
8567     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8568         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8569
8570     /*
8571      * look for win, lose, draw, or draw offer
8572      */
8573     if (strncmp(message, "1-0", 3) == 0) {
8574         char *p, *q, *r = "";
8575         p = strchr(message, '{');
8576         if (p) {
8577             q = strchr(p, '}');
8578             if (q) {
8579                 *q = NULLCHAR;
8580                 r = p + 1;
8581             }
8582         }
8583         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8584         return;
8585     } else if (strncmp(message, "0-1", 3) == 0) {
8586         char *p, *q, *r = "";
8587         p = strchr(message, '{');
8588         if (p) {
8589             q = strchr(p, '}');
8590             if (q) {
8591                 *q = NULLCHAR;
8592                 r = p + 1;
8593             }
8594         }
8595         /* Kludge for Arasan 4.1 bug */
8596         if (strcmp(r, "Black resigns") == 0) {
8597             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8598             return;
8599         }
8600         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8601         return;
8602     } else if (strncmp(message, "1/2", 3) == 0) {
8603         char *p, *q, *r = "";
8604         p = strchr(message, '{');
8605         if (p) {
8606             q = strchr(p, '}');
8607             if (q) {
8608                 *q = NULLCHAR;
8609                 r = p + 1;
8610             }
8611         }
8612
8613         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8614         return;
8615
8616     } else if (strncmp(message, "White resign", 12) == 0) {
8617         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8618         return;
8619     } else if (strncmp(message, "Black resign", 12) == 0) {
8620         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8621         return;
8622     } else if (strncmp(message, "White matches", 13) == 0 ||
8623                strncmp(message, "Black matches", 13) == 0   ) {
8624         /* [HGM] ignore GNUShogi noises */
8625         return;
8626     } else if (strncmp(message, "White", 5) == 0 &&
8627                message[5] != '(' &&
8628                StrStr(message, "Black") == NULL) {
8629         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8630         return;
8631     } else if (strncmp(message, "Black", 5) == 0 &&
8632                message[5] != '(') {
8633         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8634         return;
8635     } else if (strcmp(message, "resign") == 0 ||
8636                strcmp(message, "computer resigns") == 0) {
8637         switch (gameMode) {
8638           case MachinePlaysBlack:
8639           case IcsPlayingBlack:
8640             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8641             break;
8642           case MachinePlaysWhite:
8643           case IcsPlayingWhite:
8644             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8645             break;
8646           case TwoMachinesPlay:
8647             if (cps->twoMachinesColor[0] == 'w')
8648               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8649             else
8650               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8651             break;
8652           default:
8653             /* can't happen */
8654             break;
8655         }
8656         return;
8657     } else if (strncmp(message, "opponent mates", 14) == 0) {
8658         switch (gameMode) {
8659           case MachinePlaysBlack:
8660           case IcsPlayingBlack:
8661             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8662             break;
8663           case MachinePlaysWhite:
8664           case IcsPlayingWhite:
8665             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8666             break;
8667           case TwoMachinesPlay:
8668             if (cps->twoMachinesColor[0] == 'w')
8669               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8670             else
8671               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8672             break;
8673           default:
8674             /* can't happen */
8675             break;
8676         }
8677         return;
8678     } else if (strncmp(message, "computer mates", 14) == 0) {
8679         switch (gameMode) {
8680           case MachinePlaysBlack:
8681           case IcsPlayingBlack:
8682             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8683             break;
8684           case MachinePlaysWhite:
8685           case IcsPlayingWhite:
8686             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8687             break;
8688           case TwoMachinesPlay:
8689             if (cps->twoMachinesColor[0] == 'w')
8690               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8691             else
8692               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8693             break;
8694           default:
8695             /* can't happen */
8696             break;
8697         }
8698         return;
8699     } else if (strncmp(message, "checkmate", 9) == 0) {
8700         if (WhiteOnMove(forwardMostMove)) {
8701             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8702         } else {
8703             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8704         }
8705         return;
8706     } else if (strstr(message, "Draw") != NULL ||
8707                strstr(message, "game is a draw") != NULL) {
8708         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8709         return;
8710     } else if (strstr(message, "offer") != NULL &&
8711                strstr(message, "draw") != NULL) {
8712 #if ZIPPY
8713         if (appData.zippyPlay && first.initDone) {
8714             /* Relay offer to ICS */
8715             SendToICS(ics_prefix);
8716             SendToICS("draw\n");
8717         }
8718 #endif
8719         cps->offeredDraw = 2; /* valid until this engine moves twice */
8720         if (gameMode == TwoMachinesPlay) {
8721             if (cps->other->offeredDraw) {
8722                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8723             /* [HGM] in two-machine mode we delay relaying draw offer      */
8724             /* until after we also have move, to see if it is really claim */
8725             }
8726         } else if (gameMode == MachinePlaysWhite ||
8727                    gameMode == MachinePlaysBlack) {
8728           if (userOfferedDraw) {
8729             DisplayInformation(_("Machine accepts your draw offer"));
8730             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8731           } else {
8732             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8733           }
8734         }
8735     }
8736
8737
8738     /*
8739      * Look for thinking output
8740      */
8741     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8742           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8743                                 ) {
8744         int plylev, mvleft, mvtot, curscore, time;
8745         char mvname[MOVE_LEN];
8746         u64 nodes; // [DM]
8747         char plyext;
8748         int ignore = FALSE;
8749         int prefixHint = FALSE;
8750         mvname[0] = NULLCHAR;
8751
8752         switch (gameMode) {
8753           case MachinePlaysBlack:
8754           case IcsPlayingBlack:
8755             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8756             break;
8757           case MachinePlaysWhite:
8758           case IcsPlayingWhite:
8759             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8760             break;
8761           case AnalyzeMode:
8762           case AnalyzeFile:
8763             break;
8764           case IcsObserving: /* [DM] icsEngineAnalyze */
8765             if (!appData.icsEngineAnalyze) ignore = TRUE;
8766             break;
8767           case TwoMachinesPlay:
8768             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8769                 ignore = TRUE;
8770             }
8771             break;
8772           default:
8773             ignore = TRUE;
8774             break;
8775         }
8776
8777         if (!ignore) {
8778             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8779             buf1[0] = NULLCHAR;
8780             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8781                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8782
8783                 if (plyext != ' ' && plyext != '\t') {
8784                     time *= 100;
8785                 }
8786
8787                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8788                 if( cps->scoreIsAbsolute &&
8789                     ( gameMode == MachinePlaysBlack ||
8790                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8791                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8792                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8793                      !WhiteOnMove(currentMove)
8794                     ) )
8795                 {
8796                     curscore = -curscore;
8797                 }
8798
8799                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8800
8801                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8802                         char buf[MSG_SIZ];
8803                         FILE *f;
8804                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8805                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8806                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8807                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8808                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8809                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8810                                 fclose(f);
8811                         } else DisplayError(_("failed writing PV"), 0);
8812                 }
8813
8814                 tempStats.depth = plylev;
8815                 tempStats.nodes = nodes;
8816                 tempStats.time = time;
8817                 tempStats.score = curscore;
8818                 tempStats.got_only_move = 0;
8819
8820                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8821                         int ticklen;
8822
8823                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8824                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8825                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8826                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8827                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8828                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8829                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8830                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8831                 }
8832
8833                 /* Buffer overflow protection */
8834                 if (pv[0] != NULLCHAR) {
8835                     if (strlen(pv) >= sizeof(tempStats.movelist)
8836                         && appData.debugMode) {
8837                         fprintf(debugFP,
8838                                 "PV is too long; using the first %u bytes.\n",
8839                                 (unsigned) sizeof(tempStats.movelist) - 1);
8840                     }
8841
8842                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8843                 } else {
8844                     sprintf(tempStats.movelist, " no PV\n");
8845                 }
8846
8847                 if (tempStats.seen_stat) {
8848                     tempStats.ok_to_send = 1;
8849                 }
8850
8851                 if (strchr(tempStats.movelist, '(') != NULL) {
8852                     tempStats.line_is_book = 1;
8853                     tempStats.nr_moves = 0;
8854                     tempStats.moves_left = 0;
8855                 } else {
8856                     tempStats.line_is_book = 0;
8857                 }
8858
8859                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8860                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8861
8862                 SendProgramStatsToFrontend( cps, &tempStats );
8863
8864                 /*
8865                     [AS] Protect the thinkOutput buffer from overflow... this
8866                     is only useful if buf1 hasn't overflowed first!
8867                 */
8868                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8869                          plylev,
8870                          (gameMode == TwoMachinesPlay ?
8871                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8872                          ((double) curscore) / 100.0,
8873                          prefixHint ? lastHint : "",
8874                          prefixHint ? " " : "" );
8875
8876                 if( buf1[0] != NULLCHAR ) {
8877                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8878
8879                     if( strlen(pv) > max_len ) {
8880                         if( appData.debugMode) {
8881                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8882                         }
8883                         pv[max_len+1] = '\0';
8884                     }
8885
8886                     strcat( thinkOutput, pv);
8887                 }
8888
8889                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8890                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8891                     DisplayMove(currentMove - 1);
8892                 }
8893                 return;
8894
8895             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8896                 /* crafty (9.25+) says "(only move) <move>"
8897                  * if there is only 1 legal move
8898                  */
8899                 sscanf(p, "(only move) %s", buf1);
8900                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8901                 sprintf(programStats.movelist, "%s (only move)", buf1);
8902                 programStats.depth = 1;
8903                 programStats.nr_moves = 1;
8904                 programStats.moves_left = 1;
8905                 programStats.nodes = 1;
8906                 programStats.time = 1;
8907                 programStats.got_only_move = 1;
8908
8909                 /* Not really, but we also use this member to
8910                    mean "line isn't going to change" (Crafty
8911                    isn't searching, so stats won't change) */
8912                 programStats.line_is_book = 1;
8913
8914                 SendProgramStatsToFrontend( cps, &programStats );
8915
8916                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8917                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8918                     DisplayMove(currentMove - 1);
8919                 }
8920                 return;
8921             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8922                               &time, &nodes, &plylev, &mvleft,
8923                               &mvtot, mvname) >= 5) {
8924                 /* The stat01: line is from Crafty (9.29+) in response
8925                    to the "." command */
8926                 programStats.seen_stat = 1;
8927                 cps->maybeThinking = TRUE;
8928
8929                 if (programStats.got_only_move || !appData.periodicUpdates)
8930                   return;
8931
8932                 programStats.depth = plylev;
8933                 programStats.time = time;
8934                 programStats.nodes = nodes;
8935                 programStats.moves_left = mvleft;
8936                 programStats.nr_moves = mvtot;
8937                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8938                 programStats.ok_to_send = 1;
8939                 programStats.movelist[0] = '\0';
8940
8941                 SendProgramStatsToFrontend( cps, &programStats );
8942
8943                 return;
8944
8945             } else if (strncmp(message,"++",2) == 0) {
8946                 /* Crafty 9.29+ outputs this */
8947                 programStats.got_fail = 2;
8948                 return;
8949
8950             } else if (strncmp(message,"--",2) == 0) {
8951                 /* Crafty 9.29+ outputs this */
8952                 programStats.got_fail = 1;
8953                 return;
8954
8955             } else if (thinkOutput[0] != NULLCHAR &&
8956                        strncmp(message, "    ", 4) == 0) {
8957                 unsigned message_len;
8958
8959                 p = message;
8960                 while (*p && *p == ' ') p++;
8961
8962                 message_len = strlen( p );
8963
8964                 /* [AS] Avoid buffer overflow */
8965                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8966                     strcat(thinkOutput, " ");
8967                     strcat(thinkOutput, p);
8968                 }
8969
8970                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8971                     strcat(programStats.movelist, " ");
8972                     strcat(programStats.movelist, p);
8973                 }
8974
8975                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8976                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8977                     DisplayMove(currentMove - 1);
8978                 }
8979                 return;
8980             }
8981         }
8982         else {
8983             buf1[0] = NULLCHAR;
8984
8985             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8986                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8987             {
8988                 ChessProgramStats cpstats;
8989
8990                 if (plyext != ' ' && plyext != '\t') {
8991                     time *= 100;
8992                 }
8993
8994                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8995                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8996                     curscore = -curscore;
8997                 }
8998
8999                 cpstats.depth = plylev;
9000                 cpstats.nodes = nodes;
9001                 cpstats.time = time;
9002                 cpstats.score = curscore;
9003                 cpstats.got_only_move = 0;
9004                 cpstats.movelist[0] = '\0';
9005
9006                 if (buf1[0] != NULLCHAR) {
9007                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9008                 }
9009
9010                 cpstats.ok_to_send = 0;
9011                 cpstats.line_is_book = 0;
9012                 cpstats.nr_moves = 0;
9013                 cpstats.moves_left = 0;
9014
9015                 SendProgramStatsToFrontend( cps, &cpstats );
9016             }
9017         }
9018     }
9019 }
9020
9021
9022 /* Parse a game score from the character string "game", and
9023    record it as the history of the current game.  The game
9024    score is NOT assumed to start from the standard position.
9025    The display is not updated in any way.
9026    */
9027 void
9028 ParseGameHistory (char *game)
9029 {
9030     ChessMove moveType;
9031     int fromX, fromY, toX, toY, boardIndex;
9032     char promoChar;
9033     char *p, *q;
9034     char buf[MSG_SIZ];
9035
9036     if (appData.debugMode)
9037       fprintf(debugFP, "Parsing game history: %s\n", game);
9038
9039     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9040     gameInfo.site = StrSave(appData.icsHost);
9041     gameInfo.date = PGNDate();
9042     gameInfo.round = StrSave("-");
9043
9044     /* Parse out names of players */
9045     while (*game == ' ') game++;
9046     p = buf;
9047     while (*game != ' ') *p++ = *game++;
9048     *p = NULLCHAR;
9049     gameInfo.white = StrSave(buf);
9050     while (*game == ' ') game++;
9051     p = buf;
9052     while (*game != ' ' && *game != '\n') *p++ = *game++;
9053     *p = NULLCHAR;
9054     gameInfo.black = StrSave(buf);
9055
9056     /* Parse moves */
9057     boardIndex = blackPlaysFirst ? 1 : 0;
9058     yynewstr(game);
9059     for (;;) {
9060         yyboardindex = boardIndex;
9061         moveType = (ChessMove) Myylex();
9062         switch (moveType) {
9063           case IllegalMove:             /* maybe suicide chess, etc. */
9064   if (appData.debugMode) {
9065     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9066     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9067     setbuf(debugFP, NULL);
9068   }
9069           case WhitePromotion:
9070           case BlackPromotion:
9071           case WhiteNonPromotion:
9072           case BlackNonPromotion:
9073           case NormalMove:
9074           case WhiteCapturesEnPassant:
9075           case BlackCapturesEnPassant:
9076           case WhiteKingSideCastle:
9077           case WhiteQueenSideCastle:
9078           case BlackKingSideCastle:
9079           case BlackQueenSideCastle:
9080           case WhiteKingSideCastleWild:
9081           case WhiteQueenSideCastleWild:
9082           case BlackKingSideCastleWild:
9083           case BlackQueenSideCastleWild:
9084           /* PUSH Fabien */
9085           case WhiteHSideCastleFR:
9086           case WhiteASideCastleFR:
9087           case BlackHSideCastleFR:
9088           case BlackASideCastleFR:
9089           /* POP Fabien */
9090             fromX = currentMoveString[0] - AAA;
9091             fromY = currentMoveString[1] - ONE;
9092             toX = currentMoveString[2] - AAA;
9093             toY = currentMoveString[3] - ONE;
9094             promoChar = currentMoveString[4];
9095             break;
9096           case WhiteDrop:
9097           case BlackDrop:
9098             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9099             fromX = moveType == WhiteDrop ?
9100               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9101             (int) CharToPiece(ToLower(currentMoveString[0]));
9102             fromY = DROP_RANK;
9103             toX = currentMoveString[2] - AAA;
9104             toY = currentMoveString[3] - ONE;
9105             promoChar = NULLCHAR;
9106             break;
9107           case AmbiguousMove:
9108             /* bug? */
9109             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9110   if (appData.debugMode) {
9111     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9112     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9113     setbuf(debugFP, NULL);
9114   }
9115             DisplayError(buf, 0);
9116             return;
9117           case ImpossibleMove:
9118             /* bug? */
9119             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9120   if (appData.debugMode) {
9121     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9122     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9123     setbuf(debugFP, NULL);
9124   }
9125             DisplayError(buf, 0);
9126             return;
9127           case EndOfFile:
9128             if (boardIndex < backwardMostMove) {
9129                 /* Oops, gap.  How did that happen? */
9130                 DisplayError(_("Gap in move list"), 0);
9131                 return;
9132             }
9133             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9134             if (boardIndex > forwardMostMove) {
9135                 forwardMostMove = boardIndex;
9136             }
9137             return;
9138           case ElapsedTime:
9139             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9140                 strcat(parseList[boardIndex-1], " ");
9141                 strcat(parseList[boardIndex-1], yy_text);
9142             }
9143             continue;
9144           case Comment:
9145           case PGNTag:
9146           case NAG:
9147           default:
9148             /* ignore */
9149             continue;
9150           case WhiteWins:
9151           case BlackWins:
9152           case GameIsDrawn:
9153           case GameUnfinished:
9154             if (gameMode == IcsExamining) {
9155                 if (boardIndex < backwardMostMove) {
9156                     /* Oops, gap.  How did that happen? */
9157                     return;
9158                 }
9159                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9160                 return;
9161             }
9162             gameInfo.result = moveType;
9163             p = strchr(yy_text, '{');
9164             if (p == NULL) p = strchr(yy_text, '(');
9165             if (p == NULL) {
9166                 p = yy_text;
9167                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9168             } else {
9169                 q = strchr(p, *p == '{' ? '}' : ')');
9170                 if (q != NULL) *q = NULLCHAR;
9171                 p++;
9172             }
9173             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9174             gameInfo.resultDetails = StrSave(p);
9175             continue;
9176         }
9177         if (boardIndex >= forwardMostMove &&
9178             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9179             backwardMostMove = blackPlaysFirst ? 1 : 0;
9180             return;
9181         }
9182         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9183                                  fromY, fromX, toY, toX, promoChar,
9184                                  parseList[boardIndex]);
9185         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9186         /* currentMoveString is set as a side-effect of yylex */
9187         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9188         strcat(moveList[boardIndex], "\n");
9189         boardIndex++;
9190         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9191         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9192           case MT_NONE:
9193           case MT_STALEMATE:
9194           default:
9195             break;
9196           case MT_CHECK:
9197             if(gameInfo.variant != VariantShogi)
9198                 strcat(parseList[boardIndex - 1], "+");
9199             break;
9200           case MT_CHECKMATE:
9201           case MT_STAINMATE:
9202             strcat(parseList[boardIndex - 1], "#");
9203             break;
9204         }
9205     }
9206 }
9207
9208
9209 /* Apply a move to the given board  */
9210 void
9211 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9212 {
9213   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9214   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9215
9216     /* [HGM] compute & store e.p. status and castling rights for new position */
9217     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9218
9219       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9220       oldEP = (signed char)board[EP_STATUS];
9221       board[EP_STATUS] = EP_NONE;
9222
9223   if (fromY == DROP_RANK) {
9224         /* must be first */
9225         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9226             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9227             return;
9228         }
9229         piece = board[toY][toX] = (ChessSquare) fromX;
9230   } else {
9231       int i;
9232
9233       if( board[toY][toX] != EmptySquare )
9234            board[EP_STATUS] = EP_CAPTURE;
9235
9236       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9237            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9238                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9239       } else
9240       if( board[fromY][fromX] == WhitePawn ) {
9241            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9242                board[EP_STATUS] = EP_PAWN_MOVE;
9243            if( toY-fromY==2) {
9244                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9245                         gameInfo.variant != VariantBerolina || toX < fromX)
9246                       board[EP_STATUS] = toX | berolina;
9247                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9248                         gameInfo.variant != VariantBerolina || toX > fromX)
9249                       board[EP_STATUS] = toX;
9250            }
9251       } else
9252       if( board[fromY][fromX] == BlackPawn ) {
9253            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9254                board[EP_STATUS] = EP_PAWN_MOVE;
9255            if( toY-fromY== -2) {
9256                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9257                         gameInfo.variant != VariantBerolina || toX < fromX)
9258                       board[EP_STATUS] = toX | berolina;
9259                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9260                         gameInfo.variant != VariantBerolina || toX > fromX)
9261                       board[EP_STATUS] = toX;
9262            }
9263        }
9264
9265        for(i=0; i<nrCastlingRights; i++) {
9266            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9267               board[CASTLING][i] == toX   && castlingRank[i] == toY
9268              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9269        }
9270
9271      if (fromX == toX && fromY == toY) return;
9272
9273      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9274      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9275      if(gameInfo.variant == VariantKnightmate)
9276          king += (int) WhiteUnicorn - (int) WhiteKing;
9277
9278     /* Code added by Tord: */
9279     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9280     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9281         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9282       board[fromY][fromX] = EmptySquare;
9283       board[toY][toX] = EmptySquare;
9284       if((toX > fromX) != (piece == WhiteRook)) {
9285         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9286       } else {
9287         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9288       }
9289     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9290                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9291       board[fromY][fromX] = EmptySquare;
9292       board[toY][toX] = EmptySquare;
9293       if((toX > fromX) != (piece == BlackRook)) {
9294         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9295       } else {
9296         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9297       }
9298     /* End of code added by Tord */
9299
9300     } else if (board[fromY][fromX] == king
9301         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9302         && toY == fromY && toX > fromX+1) {
9303         board[fromY][fromX] = EmptySquare;
9304         board[toY][toX] = king;
9305         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9306         board[fromY][BOARD_RGHT-1] = EmptySquare;
9307     } else if (board[fromY][fromX] == king
9308         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9309                && toY == fromY && toX < fromX-1) {
9310         board[fromY][fromX] = EmptySquare;
9311         board[toY][toX] = king;
9312         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9313         board[fromY][BOARD_LEFT] = EmptySquare;
9314     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9315                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9316                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9317                ) {
9318         /* white pawn promotion */
9319         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9320         if(gameInfo.variant==VariantBughouse ||
9321            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9322             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9323         board[fromY][fromX] = EmptySquare;
9324     } else if ((fromY >= BOARD_HEIGHT>>1)
9325                && (toX != fromX)
9326                && gameInfo.variant != VariantXiangqi
9327                && gameInfo.variant != VariantBerolina
9328                && (board[fromY][fromX] == WhitePawn)
9329                && (board[toY][toX] == EmptySquare)) {
9330         board[fromY][fromX] = EmptySquare;
9331         board[toY][toX] = WhitePawn;
9332         captured = board[toY - 1][toX];
9333         board[toY - 1][toX] = EmptySquare;
9334     } else if ((fromY == BOARD_HEIGHT-4)
9335                && (toX == fromX)
9336                && gameInfo.variant == VariantBerolina
9337                && (board[fromY][fromX] == WhitePawn)
9338                && (board[toY][toX] == EmptySquare)) {
9339         board[fromY][fromX] = EmptySquare;
9340         board[toY][toX] = WhitePawn;
9341         if(oldEP & EP_BEROLIN_A) {
9342                 captured = board[fromY][fromX-1];
9343                 board[fromY][fromX-1] = EmptySquare;
9344         }else{  captured = board[fromY][fromX+1];
9345                 board[fromY][fromX+1] = EmptySquare;
9346         }
9347     } else if (board[fromY][fromX] == king
9348         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9349                && toY == fromY && toX > fromX+1) {
9350         board[fromY][fromX] = EmptySquare;
9351         board[toY][toX] = king;
9352         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9353         board[fromY][BOARD_RGHT-1] = EmptySquare;
9354     } else if (board[fromY][fromX] == king
9355         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9356                && toY == fromY && toX < fromX-1) {
9357         board[fromY][fromX] = EmptySquare;
9358         board[toY][toX] = king;
9359         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9360         board[fromY][BOARD_LEFT] = EmptySquare;
9361     } else if (fromY == 7 && fromX == 3
9362                && board[fromY][fromX] == BlackKing
9363                && toY == 7 && toX == 5) {
9364         board[fromY][fromX] = EmptySquare;
9365         board[toY][toX] = BlackKing;
9366         board[fromY][7] = EmptySquare;
9367         board[toY][4] = BlackRook;
9368     } else if (fromY == 7 && fromX == 3
9369                && board[fromY][fromX] == BlackKing
9370                && toY == 7 && toX == 1) {
9371         board[fromY][fromX] = EmptySquare;
9372         board[toY][toX] = BlackKing;
9373         board[fromY][0] = EmptySquare;
9374         board[toY][2] = BlackRook;
9375     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9376                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9377                && toY < promoRank && promoChar
9378                ) {
9379         /* black pawn promotion */
9380         board[toY][toX] = CharToPiece(ToLower(promoChar));
9381         if(gameInfo.variant==VariantBughouse ||
9382            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9383             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9384         board[fromY][fromX] = EmptySquare;
9385     } else if ((fromY < BOARD_HEIGHT>>1)
9386                && (toX != fromX)
9387                && gameInfo.variant != VariantXiangqi
9388                && gameInfo.variant != VariantBerolina
9389                && (board[fromY][fromX] == BlackPawn)
9390                && (board[toY][toX] == EmptySquare)) {
9391         board[fromY][fromX] = EmptySquare;
9392         board[toY][toX] = BlackPawn;
9393         captured = board[toY + 1][toX];
9394         board[toY + 1][toX] = EmptySquare;
9395     } else if ((fromY == 3)
9396                && (toX == fromX)
9397                && gameInfo.variant == VariantBerolina
9398                && (board[fromY][fromX] == BlackPawn)
9399                && (board[toY][toX] == EmptySquare)) {
9400         board[fromY][fromX] = EmptySquare;
9401         board[toY][toX] = BlackPawn;
9402         if(oldEP & EP_BEROLIN_A) {
9403                 captured = board[fromY][fromX-1];
9404                 board[fromY][fromX-1] = EmptySquare;
9405         }else{  captured = board[fromY][fromX+1];
9406                 board[fromY][fromX+1] = EmptySquare;
9407         }
9408     } else {
9409         board[toY][toX] = board[fromY][fromX];
9410         board[fromY][fromX] = EmptySquare;
9411     }
9412   }
9413
9414     if (gameInfo.holdingsWidth != 0) {
9415
9416       /* !!A lot more code needs to be written to support holdings  */
9417       /* [HGM] OK, so I have written it. Holdings are stored in the */
9418       /* penultimate board files, so they are automaticlly stored   */
9419       /* in the game history.                                       */
9420       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9421                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9422         /* Delete from holdings, by decreasing count */
9423         /* and erasing image if necessary            */
9424         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9425         if(p < (int) BlackPawn) { /* white drop */
9426              p -= (int)WhitePawn;
9427                  p = PieceToNumber((ChessSquare)p);
9428              if(p >= gameInfo.holdingsSize) p = 0;
9429              if(--board[p][BOARD_WIDTH-2] <= 0)
9430                   board[p][BOARD_WIDTH-1] = EmptySquare;
9431              if((int)board[p][BOARD_WIDTH-2] < 0)
9432                         board[p][BOARD_WIDTH-2] = 0;
9433         } else {                  /* black drop */
9434              p -= (int)BlackPawn;
9435                  p = PieceToNumber((ChessSquare)p);
9436              if(p >= gameInfo.holdingsSize) p = 0;
9437              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9438                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9439              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9440                         board[BOARD_HEIGHT-1-p][1] = 0;
9441         }
9442       }
9443       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9444           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9445         /* [HGM] holdings: Add to holdings, if holdings exist */
9446         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9447                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9448                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9449         }
9450         p = (int) captured;
9451         if (p >= (int) BlackPawn) {
9452           p -= (int)BlackPawn;
9453           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9454                   /* in Shogi restore piece to its original  first */
9455                   captured = (ChessSquare) (DEMOTED captured);
9456                   p = DEMOTED p;
9457           }
9458           p = PieceToNumber((ChessSquare)p);
9459           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9460           board[p][BOARD_WIDTH-2]++;
9461           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9462         } else {
9463           p -= (int)WhitePawn;
9464           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9465                   captured = (ChessSquare) (DEMOTED captured);
9466                   p = DEMOTED p;
9467           }
9468           p = PieceToNumber((ChessSquare)p);
9469           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9470           board[BOARD_HEIGHT-1-p][1]++;
9471           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9472         }
9473       }
9474     } else if (gameInfo.variant == VariantAtomic) {
9475       if (captured != EmptySquare) {
9476         int y, x;
9477         for (y = toY-1; y <= toY+1; y++) {
9478           for (x = toX-1; x <= toX+1; x++) {
9479             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9480                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9481               board[y][x] = EmptySquare;
9482             }
9483           }
9484         }
9485         board[toY][toX] = EmptySquare;
9486       }
9487     }
9488     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9489         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9490     } else
9491     if(promoChar == '+') {
9492         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9493         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9494     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9495         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9496         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9497            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9498         board[toY][toX] = newPiece;
9499     }
9500     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9501                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9502         // [HGM] superchess: take promotion piece out of holdings
9503         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9504         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9505             if(!--board[k][BOARD_WIDTH-2])
9506                 board[k][BOARD_WIDTH-1] = EmptySquare;
9507         } else {
9508             if(!--board[BOARD_HEIGHT-1-k][1])
9509                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9510         }
9511     }
9512
9513 }
9514
9515 /* Updates forwardMostMove */
9516 void
9517 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9518 {
9519 //    forwardMostMove++; // [HGM] bare: moved downstream
9520
9521     (void) CoordsToAlgebraic(boards[forwardMostMove],
9522                              PosFlags(forwardMostMove),
9523                              fromY, fromX, toY, toX, promoChar,
9524                              parseList[forwardMostMove]);
9525
9526     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9527         int timeLeft; static int lastLoadFlag=0; int king, piece;
9528         piece = boards[forwardMostMove][fromY][fromX];
9529         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9530         if(gameInfo.variant == VariantKnightmate)
9531             king += (int) WhiteUnicorn - (int) WhiteKing;
9532         if(forwardMostMove == 0) {
9533             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9534                 fprintf(serverMoves, "%s;", UserName());
9535             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9536                 fprintf(serverMoves, "%s;", second.tidy);
9537             fprintf(serverMoves, "%s;", first.tidy);
9538             if(gameMode == MachinePlaysWhite)
9539                 fprintf(serverMoves, "%s;", UserName());
9540             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9541                 fprintf(serverMoves, "%s;", second.tidy);
9542         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9543         lastLoadFlag = loadFlag;
9544         // print base move
9545         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9546         // print castling suffix
9547         if( toY == fromY && piece == king ) {
9548             if(toX-fromX > 1)
9549                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9550             if(fromX-toX >1)
9551                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9552         }
9553         // e.p. suffix
9554         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9555              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9556              boards[forwardMostMove][toY][toX] == EmptySquare
9557              && fromX != toX && fromY != toY)
9558                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9559         // promotion suffix
9560         if(promoChar != NULLCHAR)
9561                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9562         if(!loadFlag) {
9563                 char buf[MOVE_LEN*2], *p; int len;
9564             fprintf(serverMoves, "/%d/%d",
9565                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9566             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9567             else                      timeLeft = blackTimeRemaining/1000;
9568             fprintf(serverMoves, "/%d", timeLeft);
9569                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9570                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9571                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9572             fprintf(serverMoves, "/%s", buf);
9573         }
9574         fflush(serverMoves);
9575     }
9576
9577     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9578         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9579       return;
9580     }
9581     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9582     if (commentList[forwardMostMove+1] != NULL) {
9583         free(commentList[forwardMostMove+1]);
9584         commentList[forwardMostMove+1] = NULL;
9585     }
9586     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9587     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9588     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9589     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9590     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9591     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9592     adjustedClock = FALSE;
9593     gameInfo.result = GameUnfinished;
9594     if (gameInfo.resultDetails != NULL) {
9595         free(gameInfo.resultDetails);
9596         gameInfo.resultDetails = NULL;
9597     }
9598     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9599                               moveList[forwardMostMove - 1]);
9600     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9601       case MT_NONE:
9602       case MT_STALEMATE:
9603       default:
9604         break;
9605       case MT_CHECK:
9606         if(gameInfo.variant != VariantShogi)
9607             strcat(parseList[forwardMostMove - 1], "+");
9608         break;
9609       case MT_CHECKMATE:
9610       case MT_STAINMATE:
9611         strcat(parseList[forwardMostMove - 1], "#");
9612         break;
9613     }
9614
9615 }
9616
9617 /* Updates currentMove if not pausing */
9618 void
9619 ShowMove (int fromX, int fromY, int toX, int toY)
9620 {
9621     int instant = (gameMode == PlayFromGameFile) ?
9622         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9623     if(appData.noGUI) return;
9624     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9625         if (!instant) {
9626             if (forwardMostMove == currentMove + 1) {
9627                 AnimateMove(boards[forwardMostMove - 1],
9628                             fromX, fromY, toX, toY);
9629             }
9630             if (appData.highlightLastMove) {
9631                 SetHighlights(fromX, fromY, toX, toY);
9632             }
9633         }
9634         currentMove = forwardMostMove;
9635     }
9636
9637     if (instant) return;
9638
9639     DisplayMove(currentMove - 1);
9640     DrawPosition(FALSE, boards[currentMove]);
9641     DisplayBothClocks();
9642     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9643 }
9644
9645 void
9646 SendEgtPath (ChessProgramState *cps)
9647 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9648         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9649
9650         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9651
9652         while(*p) {
9653             char c, *q = name+1, *r, *s;
9654
9655             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9656             while(*p && *p != ',') *q++ = *p++;
9657             *q++ = ':'; *q = 0;
9658             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9659                 strcmp(name, ",nalimov:") == 0 ) {
9660                 // take nalimov path from the menu-changeable option first, if it is defined
9661               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9662                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9663             } else
9664             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9665                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9666                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9667                 s = r = StrStr(s, ":") + 1; // beginning of path info
9668                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9669                 c = *r; *r = 0;             // temporarily null-terminate path info
9670                     *--q = 0;               // strip of trailig ':' from name
9671                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9672                 *r = c;
9673                 SendToProgram(buf,cps);     // send egtbpath command for this format
9674             }
9675             if(*p == ',') p++; // read away comma to position for next format name
9676         }
9677 }
9678
9679 void
9680 InitChessProgram (ChessProgramState *cps, int setup)
9681 /* setup needed to setup FRC opening position */
9682 {
9683     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9684     if (appData.noChessProgram) return;
9685     hintRequested = FALSE;
9686     bookRequested = FALSE;
9687
9688     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9689     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9690     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9691     if(cps->memSize) { /* [HGM] memory */
9692       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9693         SendToProgram(buf, cps);
9694     }
9695     SendEgtPath(cps); /* [HGM] EGT */
9696     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9697       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9698         SendToProgram(buf, cps);
9699     }
9700
9701     SendToProgram(cps->initString, cps);
9702     if (gameInfo.variant != VariantNormal &&
9703         gameInfo.variant != VariantLoadable
9704         /* [HGM] also send variant if board size non-standard */
9705         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9706                                             ) {
9707       char *v = VariantName(gameInfo.variant);
9708       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9709         /* [HGM] in protocol 1 we have to assume all variants valid */
9710         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9711         DisplayFatalError(buf, 0, 1);
9712         return;
9713       }
9714
9715       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9716       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9717       if( gameInfo.variant == VariantXiangqi )
9718            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9719       if( gameInfo.variant == VariantShogi )
9720            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9721       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9722            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9723       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9724           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9725            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9726       if( gameInfo.variant == VariantCourier )
9727            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9728       if( gameInfo.variant == VariantSuper )
9729            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9730       if( gameInfo.variant == VariantGreat )
9731            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9732       if( gameInfo.variant == VariantSChess )
9733            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9734       if( gameInfo.variant == VariantGrand )
9735            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9736
9737       if(overruled) {
9738         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9739                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9740            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9741            if(StrStr(cps->variants, b) == NULL) {
9742                // specific sized variant not known, check if general sizing allowed
9743                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9744                    if(StrStr(cps->variants, "boardsize") == NULL) {
9745                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9746                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9747                        DisplayFatalError(buf, 0, 1);
9748                        return;
9749                    }
9750                    /* [HGM] here we really should compare with the maximum supported board size */
9751                }
9752            }
9753       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9754       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9755       SendToProgram(buf, cps);
9756     }
9757     currentlyInitializedVariant = gameInfo.variant;
9758
9759     /* [HGM] send opening position in FRC to first engine */
9760     if(setup) {
9761           SendToProgram("force\n", cps);
9762           SendBoard(cps, 0);
9763           /* engine is now in force mode! Set flag to wake it up after first move. */
9764           setboardSpoiledMachineBlack = 1;
9765     }
9766
9767     if (cps->sendICS) {
9768       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9769       SendToProgram(buf, cps);
9770     }
9771     cps->maybeThinking = FALSE;
9772     cps->offeredDraw = 0;
9773     if (!appData.icsActive) {
9774         SendTimeControl(cps, movesPerSession, timeControl,
9775                         timeIncrement, appData.searchDepth,
9776                         searchTime);
9777     }
9778     if (appData.showThinking
9779         // [HGM] thinking: four options require thinking output to be sent
9780         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9781                                 ) {
9782         SendToProgram("post\n", cps);
9783     }
9784     SendToProgram("hard\n", cps);
9785     if (!appData.ponderNextMove) {
9786         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9787            it without being sure what state we are in first.  "hard"
9788            is not a toggle, so that one is OK.
9789          */
9790         SendToProgram("easy\n", cps);
9791     }
9792     if (cps->usePing) {
9793       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9794       SendToProgram(buf, cps);
9795     }
9796     cps->initDone = TRUE;
9797     ClearEngineOutputPane(cps == &second);
9798 }
9799
9800
9801 void
9802 StartChessProgram (ChessProgramState *cps)
9803 {
9804     char buf[MSG_SIZ];
9805     int err;
9806
9807     if (appData.noChessProgram) return;
9808     cps->initDone = FALSE;
9809
9810     if (strcmp(cps->host, "localhost") == 0) {
9811         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9812     } else if (*appData.remoteShell == NULLCHAR) {
9813         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9814     } else {
9815         if (*appData.remoteUser == NULLCHAR) {
9816           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9817                     cps->program);
9818         } else {
9819           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9820                     cps->host, appData.remoteUser, cps->program);
9821         }
9822         err = StartChildProcess(buf, "", &cps->pr);
9823     }
9824
9825     if (err != 0) {
9826       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9827         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9828         if(cps != &first) return;
9829         appData.noChessProgram = TRUE;
9830         ThawUI();
9831         SetNCPMode();
9832 //      DisplayFatalError(buf, err, 1);
9833 //      cps->pr = NoProc;
9834 //      cps->isr = NULL;
9835         return;
9836     }
9837
9838     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9839     if (cps->protocolVersion > 1) {
9840       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9841       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9842       cps->comboCnt = 0;  //                and values of combo boxes
9843       SendToProgram(buf, cps);
9844     } else {
9845       SendToProgram("xboard\n", cps);
9846     }
9847 }
9848
9849 void
9850 TwoMachinesEventIfReady P((void))
9851 {
9852   static int curMess = 0;
9853   if (first.lastPing != first.lastPong) {
9854     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9855     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9856     return;
9857   }
9858   if (second.lastPing != second.lastPong) {
9859     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9860     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9861     return;
9862   }
9863   DisplayMessage("", ""); curMess = 0;
9864   ThawUI();
9865   TwoMachinesEvent();
9866 }
9867
9868 char *
9869 MakeName (char *template)
9870 {
9871     time_t clock;
9872     struct tm *tm;
9873     static char buf[MSG_SIZ];
9874     char *p = buf;
9875     int i;
9876
9877     clock = time((time_t *)NULL);
9878     tm = localtime(&clock);
9879
9880     while(*p++ = *template++) if(p[-1] == '%') {
9881         switch(*template++) {
9882           case 0:   *p = 0; return buf;
9883           case 'Y': i = tm->tm_year+1900; break;
9884           case 'y': i = tm->tm_year-100; break;
9885           case 'M': i = tm->tm_mon+1; break;
9886           case 'd': i = tm->tm_mday; break;
9887           case 'h': i = tm->tm_hour; break;
9888           case 'm': i = tm->tm_min; break;
9889           case 's': i = tm->tm_sec; break;
9890           default:  i = 0;
9891         }
9892         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9893     }
9894     return buf;
9895 }
9896
9897 int
9898 CountPlayers (char *p)
9899 {
9900     int n = 0;
9901     while(p = strchr(p, '\n')) p++, n++; // count participants
9902     return n;
9903 }
9904
9905 FILE *
9906 WriteTourneyFile (char *results, FILE *f)
9907 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9908     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9909     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9910         // create a file with tournament description
9911         fprintf(f, "-participants {%s}\n", appData.participants);
9912         fprintf(f, "-seedBase %d\n", appData.seedBase);
9913         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9914         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9915         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9916         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9917         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9918         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9919         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9920         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9921         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9922         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9923         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9924         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9925         if(searchTime > 0)
9926                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9927         else {
9928                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9929                 fprintf(f, "-tc %s\n", appData.timeControl);
9930                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9931         }
9932         fprintf(f, "-results \"%s\"\n", results);
9933     }
9934     return f;
9935 }
9936
9937 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9938
9939 void
9940 Substitute (char *participants, int expunge)
9941 {
9942     int i, changed, changes=0, nPlayers=0;
9943     char *p, *q, *r, buf[MSG_SIZ];
9944     if(participants == NULL) return;
9945     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9946     r = p = participants; q = appData.participants;
9947     while(*p && *p == *q) {
9948         if(*p == '\n') r = p+1, nPlayers++;
9949         p++; q++;
9950     }
9951     if(*p) { // difference
9952         while(*p && *p++ != '\n');
9953         while(*q && *q++ != '\n');
9954       changed = nPlayers;
9955         changes = 1 + (strcmp(p, q) != 0);
9956     }
9957     if(changes == 1) { // a single engine mnemonic was changed
9958         q = r; while(*q) nPlayers += (*q++ == '\n');
9959         p = buf; while(*r && (*p = *r++) != '\n') p++;
9960         *p = NULLCHAR;
9961         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9962         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9963         if(mnemonic[i]) { // The substitute is valid
9964             FILE *f;
9965             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9966                 flock(fileno(f), LOCK_EX);
9967                 ParseArgsFromFile(f);
9968                 fseek(f, 0, SEEK_SET);
9969                 FREE(appData.participants); appData.participants = participants;
9970                 if(expunge) { // erase results of replaced engine
9971                     int len = strlen(appData.results), w, b, dummy;
9972                     for(i=0; i<len; i++) {
9973                         Pairing(i, nPlayers, &w, &b, &dummy);
9974                         if((w == changed || b == changed) && appData.results[i] == '*') {
9975                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9976                             fclose(f);
9977                             return;
9978                         }
9979                     }
9980                     for(i=0; i<len; i++) {
9981                         Pairing(i, nPlayers, &w, &b, &dummy);
9982                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9983                     }
9984                 }
9985                 WriteTourneyFile(appData.results, f);
9986                 fclose(f); // release lock
9987                 return;
9988             }
9989         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9990     }
9991     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9992     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9993     free(participants);
9994     return;
9995 }
9996
9997 int
9998 CreateTourney (char *name)
9999 {
10000         FILE *f;
10001         if(matchMode && strcmp(name, appData.tourneyFile)) {
10002              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10003         }
10004         if(name[0] == NULLCHAR) {
10005             if(appData.participants[0])
10006                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10007             return 0;
10008         }
10009         f = fopen(name, "r");
10010         if(f) { // file exists
10011             ASSIGN(appData.tourneyFile, name);
10012             ParseArgsFromFile(f); // parse it
10013         } else {
10014             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10015             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10016                 DisplayError(_("Not enough participants"), 0);
10017                 return 0;
10018             }
10019             ASSIGN(appData.tourneyFile, name);
10020             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10021             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10022         }
10023         fclose(f);
10024         appData.noChessProgram = FALSE;
10025         appData.clockMode = TRUE;
10026         SetGNUMode();
10027         return 1;
10028 }
10029
10030 int
10031 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10032 {
10033     char buf[MSG_SIZ], *p, *q;
10034     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10035     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10036     skip = !all && group[0]; // if group requested, we start in skip mode
10037     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10038         p = names; q = buf; header = 0;
10039         while(*p && *p != '\n') *q++ = *p++;
10040         *q = 0;
10041         if(*p == '\n') p++;
10042         if(buf[0] == '#') {
10043             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10044             depth++; // we must be entering a new group
10045             if(all) continue; // suppress printing group headers when complete list requested
10046             header = 1;
10047             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10048         }
10049         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10050         if(engineList[i]) free(engineList[i]);
10051         engineList[i] = strdup(buf);
10052         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10053         if(engineMnemonic[i]) free(engineMnemonic[i]);
10054         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10055             strcat(buf, " (");
10056             sscanf(q + 8, "%s", buf + strlen(buf));
10057             strcat(buf, ")");
10058         }
10059         engineMnemonic[i] = strdup(buf);
10060         i++;
10061     }
10062     engineList[i] = engineMnemonic[i] = NULL;
10063     return i;
10064 }
10065
10066 // following implemented as macro to avoid type limitations
10067 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10068
10069 void
10070 SwapEngines (int n)
10071 {   // swap settings for first engine and other engine (so far only some selected options)
10072     int h;
10073     char *p;
10074     if(n == 0) return;
10075     SWAP(directory, p)
10076     SWAP(chessProgram, p)
10077     SWAP(isUCI, h)
10078     SWAP(hasOwnBookUCI, h)
10079     SWAP(protocolVersion, h)
10080     SWAP(reuse, h)
10081     SWAP(scoreIsAbsolute, h)
10082     SWAP(timeOdds, h)
10083     SWAP(logo, p)
10084     SWAP(pgnName, p)
10085     SWAP(pvSAN, h)
10086     SWAP(engOptions, p)
10087     SWAP(engInitString, p)
10088     SWAP(computerString, p)
10089     SWAP(features, p)
10090     SWAP(fenOverride, p)
10091     SWAP(NPS, h)
10092     SWAP(accumulateTC, h)
10093     SWAP(host, p)
10094 }
10095
10096 int
10097 SetPlayer (int player, char *p)
10098 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10099     int i;
10100     char buf[MSG_SIZ], *engineName;
10101     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10102     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10103     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10104     if(mnemonic[i]) {
10105         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10106         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10107         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10108         ParseArgsFromString(buf);
10109     }
10110     free(engineName);
10111     return i;
10112 }
10113
10114 char *recentEngines;
10115
10116 void
10117 RecentEngineEvent (int nr)
10118 {
10119     int n;
10120 //    SwapEngines(1); // bump first to second
10121 //    ReplaceEngine(&second, 1); // and load it there
10122     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10123     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10124     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10125         ReplaceEngine(&first, 0);
10126         FloatToFront(&appData.recentEngineList, command[n]);
10127     }
10128 }
10129
10130 int
10131 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10132 {   // determine players from game number
10133     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10134
10135     if(appData.tourneyType == 0) {
10136         roundsPerCycle = (nPlayers - 1) | 1;
10137         pairingsPerRound = nPlayers / 2;
10138     } else if(appData.tourneyType > 0) {
10139         roundsPerCycle = nPlayers - appData.tourneyType;
10140         pairingsPerRound = appData.tourneyType;
10141     }
10142     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10143     gamesPerCycle = gamesPerRound * roundsPerCycle;
10144     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10145     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10146     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10147     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10148     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10149     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10150
10151     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10152     if(appData.roundSync) *syncInterval = gamesPerRound;
10153
10154     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10155
10156     if(appData.tourneyType == 0) {
10157         if(curPairing == (nPlayers-1)/2 ) {
10158             *whitePlayer = curRound;
10159             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10160         } else {
10161             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10162             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10163             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10164             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10165         }
10166     } else if(appData.tourneyType > 1) {
10167         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10168         *whitePlayer = curRound + appData.tourneyType;
10169     } else if(appData.tourneyType > 0) {
10170         *whitePlayer = curPairing;
10171         *blackPlayer = curRound + appData.tourneyType;
10172     }
10173
10174     // take care of white/black alternation per round. 
10175     // For cycles and games this is already taken care of by default, derived from matchGame!
10176     return curRound & 1;
10177 }
10178
10179 int
10180 NextTourneyGame (int nr, int *swapColors)
10181 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10182     char *p, *q;
10183     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10184     FILE *tf;
10185     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10186     tf = fopen(appData.tourneyFile, "r");
10187     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10188     ParseArgsFromFile(tf); fclose(tf);
10189     InitTimeControls(); // TC might be altered from tourney file
10190
10191     nPlayers = CountPlayers(appData.participants); // count participants
10192     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10193     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10194
10195     if(syncInterval) {
10196         p = q = appData.results;
10197         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10198         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10199             DisplayMessage(_("Waiting for other game(s)"),"");
10200             waitingForGame = TRUE;
10201             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10202             return 0;
10203         }
10204         waitingForGame = FALSE;
10205     }
10206
10207     if(appData.tourneyType < 0) {
10208         if(nr>=0 && !pairingReceived) {
10209             char buf[1<<16];
10210             if(pairing.pr == NoProc) {
10211                 if(!appData.pairingEngine[0]) {
10212                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10213                     return 0;
10214                 }
10215                 StartChessProgram(&pairing); // starts the pairing engine
10216             }
10217             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10218             SendToProgram(buf, &pairing);
10219             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10220             SendToProgram(buf, &pairing);
10221             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10222         }
10223         pairingReceived = 0;                              // ... so we continue here 
10224         *swapColors = 0;
10225         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10226         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10227         matchGame = 1; roundNr = nr / syncInterval + 1;
10228     }
10229
10230     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10231
10232     // redefine engines, engine dir, etc.
10233     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10234     if(first.pr == NoProc) {
10235       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10236       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10237     }
10238     if(second.pr == NoProc) {
10239       SwapEngines(1);
10240       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10241       SwapEngines(1);         // and make that valid for second engine by swapping
10242       InitEngine(&second, 1);
10243     }
10244     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10245     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10246     return 1;
10247 }
10248
10249 void
10250 NextMatchGame ()
10251 {   // performs game initialization that does not invoke engines, and then tries to start the game
10252     int res, firstWhite, swapColors = 0;
10253     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10254     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
10255         char buf[MSG_SIZ];
10256         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10257         if(strcmp(buf, currentDebugFile)) { // name has changed
10258             FILE *f = fopen(buf, "w");
10259             if(f) { // if opening the new file failed, just keep using the old one
10260                 ASSIGN(currentDebugFile, buf);
10261                 fclose(debugFP);
10262                 debugFP = f;
10263             }
10264             if(appData.serverFileName) {
10265                 if(serverFP) fclose(serverFP);
10266                 serverFP = fopen(appData.serverFileName, "w");
10267                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10268                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10269             }
10270         }
10271     }
10272     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10273     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10274     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10275     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10276     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10277     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10278     Reset(FALSE, first.pr != NoProc);
10279     res = LoadGameOrPosition(matchGame); // setup game
10280     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10281     if(!res) return; // abort when bad game/pos file
10282     TwoMachinesEvent();
10283 }
10284
10285 void
10286 UserAdjudicationEvent (int result)
10287 {
10288     ChessMove gameResult = GameIsDrawn;
10289
10290     if( result > 0 ) {
10291         gameResult = WhiteWins;
10292     }
10293     else if( result < 0 ) {
10294         gameResult = BlackWins;
10295     }
10296
10297     if( gameMode == TwoMachinesPlay ) {
10298         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10299     }
10300 }
10301
10302
10303 // [HGM] save: calculate checksum of game to make games easily identifiable
10304 int
10305 StringCheckSum (char *s)
10306 {
10307         int i = 0;
10308         if(s==NULL) return 0;
10309         while(*s) i = i*259 + *s++;
10310         return i;
10311 }
10312
10313 int
10314 GameCheckSum ()
10315 {
10316         int i, sum=0;
10317         for(i=backwardMostMove; i<forwardMostMove; i++) {
10318                 sum += pvInfoList[i].depth;
10319                 sum += StringCheckSum(parseList[i]);
10320                 sum += StringCheckSum(commentList[i]);
10321                 sum *= 261;
10322         }
10323         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10324         return sum + StringCheckSum(commentList[i]);
10325 } // end of save patch
10326
10327 void
10328 GameEnds (ChessMove result, char *resultDetails, int whosays)
10329 {
10330     GameMode nextGameMode;
10331     int isIcsGame;
10332     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10333
10334     if(endingGame) return; /* [HGM] crash: forbid recursion */
10335     endingGame = 1;
10336     if(twoBoards) { // [HGM] dual: switch back to one board
10337         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10338         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10339     }
10340     if (appData.debugMode) {
10341       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10342               result, resultDetails ? resultDetails : "(null)", whosays);
10343     }
10344
10345     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10346
10347     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10348         /* If we are playing on ICS, the server decides when the
10349            game is over, but the engine can offer to draw, claim
10350            a draw, or resign.
10351          */
10352 #if ZIPPY
10353         if (appData.zippyPlay && first.initDone) {
10354             if (result == GameIsDrawn) {
10355                 /* In case draw still needs to be claimed */
10356                 SendToICS(ics_prefix);
10357                 SendToICS("draw\n");
10358             } else if (StrCaseStr(resultDetails, "resign")) {
10359                 SendToICS(ics_prefix);
10360                 SendToICS("resign\n");
10361             }
10362         }
10363 #endif
10364         endingGame = 0; /* [HGM] crash */
10365         return;
10366     }
10367
10368     /* If we're loading the game from a file, stop */
10369     if (whosays == GE_FILE) {
10370       (void) StopLoadGameTimer();
10371       gameFileFP = NULL;
10372     }
10373
10374     /* Cancel draw offers */
10375     first.offeredDraw = second.offeredDraw = 0;
10376
10377     /* If this is an ICS game, only ICS can really say it's done;
10378        if not, anyone can. */
10379     isIcsGame = (gameMode == IcsPlayingWhite ||
10380                  gameMode == IcsPlayingBlack ||
10381                  gameMode == IcsObserving    ||
10382                  gameMode == IcsExamining);
10383
10384     if (!isIcsGame || whosays == GE_ICS) {
10385         /* OK -- not an ICS game, or ICS said it was done */
10386         StopClocks();
10387         if (!isIcsGame && !appData.noChessProgram)
10388           SetUserThinkingEnables();
10389
10390         /* [HGM] if a machine claims the game end we verify this claim */
10391         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10392             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10393                 char claimer;
10394                 ChessMove trueResult = (ChessMove) -1;
10395
10396                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10397                                             first.twoMachinesColor[0] :
10398                                             second.twoMachinesColor[0] ;
10399
10400                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10401                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10402                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10403                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10404                 } else
10405                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10406                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10407                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10408                 } else
10409                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10410                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10411                 }
10412
10413                 // now verify win claims, but not in drop games, as we don't understand those yet
10414                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10415                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10416                     (result == WhiteWins && claimer == 'w' ||
10417                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10418                       if (appData.debugMode) {
10419                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10420                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10421                       }
10422                       if(result != trueResult) {
10423                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10424                               result = claimer == 'w' ? BlackWins : WhiteWins;
10425                               resultDetails = buf;
10426                       }
10427                 } else
10428                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10429                     && (forwardMostMove <= backwardMostMove ||
10430                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10431                         (claimer=='b')==(forwardMostMove&1))
10432                                                                                   ) {
10433                       /* [HGM] verify: draws that were not flagged are false claims */
10434                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10435                       result = claimer == 'w' ? BlackWins : WhiteWins;
10436                       resultDetails = buf;
10437                 }
10438                 /* (Claiming a loss is accepted no questions asked!) */
10439             }
10440             /* [HGM] bare: don't allow bare King to win */
10441             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10442                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10443                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10444                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10445                && result != GameIsDrawn)
10446             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10447                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10448                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10449                         if(p >= 0 && p <= (int)WhiteKing) k++;
10450                 }
10451                 if (appData.debugMode) {
10452                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10453                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10454                 }
10455                 if(k <= 1) {
10456                         result = GameIsDrawn;
10457                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10458                         resultDetails = buf;
10459                 }
10460             }
10461         }
10462
10463
10464         if(serverMoves != NULL && !loadFlag) { char c = '=';
10465             if(result==WhiteWins) c = '+';
10466             if(result==BlackWins) c = '-';
10467             if(resultDetails != NULL)
10468                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10469         }
10470         if (resultDetails != NULL) {
10471             gameInfo.result = result;
10472             gameInfo.resultDetails = StrSave(resultDetails);
10473
10474             /* display last move only if game was not loaded from file */
10475             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10476                 DisplayMove(currentMove - 1);
10477
10478             if (forwardMostMove != 0) {
10479                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10480                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10481                                                                 ) {
10482                     if (*appData.saveGameFile != NULLCHAR) {
10483                         SaveGameToFile(appData.saveGameFile, TRUE);
10484                     } else if (appData.autoSaveGames) {
10485                         AutoSaveGame();
10486                     }
10487                     if (*appData.savePositionFile != NULLCHAR) {
10488                         SavePositionToFile(appData.savePositionFile);
10489                     }
10490                 }
10491             }
10492
10493             /* Tell program how game ended in case it is learning */
10494             /* [HGM] Moved this to after saving the PGN, just in case */
10495             /* engine died and we got here through time loss. In that */
10496             /* case we will get a fatal error writing the pipe, which */
10497             /* would otherwise lose us the PGN.                       */
10498             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10499             /* output during GameEnds should never be fatal anymore   */
10500             if (gameMode == MachinePlaysWhite ||
10501                 gameMode == MachinePlaysBlack ||
10502                 gameMode == TwoMachinesPlay ||
10503                 gameMode == IcsPlayingWhite ||
10504                 gameMode == IcsPlayingBlack ||
10505                 gameMode == BeginningOfGame) {
10506                 char buf[MSG_SIZ];
10507                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10508                         resultDetails);
10509                 if (first.pr != NoProc) {
10510                     SendToProgram(buf, &first);
10511                 }
10512                 if (second.pr != NoProc &&
10513                     gameMode == TwoMachinesPlay) {
10514                     SendToProgram(buf, &second);
10515                 }
10516             }
10517         }
10518
10519         if (appData.icsActive) {
10520             if (appData.quietPlay &&
10521                 (gameMode == IcsPlayingWhite ||
10522                  gameMode == IcsPlayingBlack)) {
10523                 SendToICS(ics_prefix);
10524                 SendToICS("set shout 1\n");
10525             }
10526             nextGameMode = IcsIdle;
10527             ics_user_moved = FALSE;
10528             /* clean up premove.  It's ugly when the game has ended and the
10529              * premove highlights are still on the board.
10530              */
10531             if (gotPremove) {
10532               gotPremove = FALSE;
10533               ClearPremoveHighlights();
10534               DrawPosition(FALSE, boards[currentMove]);
10535             }
10536             if (whosays == GE_ICS) {
10537                 switch (result) {
10538                 case WhiteWins:
10539                     if (gameMode == IcsPlayingWhite)
10540                         PlayIcsWinSound();
10541                     else if(gameMode == IcsPlayingBlack)
10542                         PlayIcsLossSound();
10543                     break;
10544                 case BlackWins:
10545                     if (gameMode == IcsPlayingBlack)
10546                         PlayIcsWinSound();
10547                     else if(gameMode == IcsPlayingWhite)
10548                         PlayIcsLossSound();
10549                     break;
10550                 case GameIsDrawn:
10551                     PlayIcsDrawSound();
10552                     break;
10553                 default:
10554                     PlayIcsUnfinishedSound();
10555                 }
10556             }
10557         } else if (gameMode == EditGame ||
10558                    gameMode == PlayFromGameFile ||
10559                    gameMode == AnalyzeMode ||
10560                    gameMode == AnalyzeFile) {
10561             nextGameMode = gameMode;
10562         } else {
10563             nextGameMode = EndOfGame;
10564         }
10565         pausing = FALSE;
10566         ModeHighlight();
10567     } else {
10568         nextGameMode = gameMode;
10569     }
10570
10571     if (appData.noChessProgram) {
10572         gameMode = nextGameMode;
10573         ModeHighlight();
10574         endingGame = 0; /* [HGM] crash */
10575         return;
10576     }
10577
10578     if (first.reuse) {
10579         /* Put first chess program into idle state */
10580         if (first.pr != NoProc &&
10581             (gameMode == MachinePlaysWhite ||
10582              gameMode == MachinePlaysBlack ||
10583              gameMode == TwoMachinesPlay ||
10584              gameMode == IcsPlayingWhite ||
10585              gameMode == IcsPlayingBlack ||
10586              gameMode == BeginningOfGame)) {
10587             SendToProgram("force\n", &first);
10588             if (first.usePing) {
10589               char buf[MSG_SIZ];
10590               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10591               SendToProgram(buf, &first);
10592             }
10593         }
10594     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10595         /* Kill off first chess program */
10596         if (first.isr != NULL)
10597           RemoveInputSource(first.isr);
10598         first.isr = NULL;
10599
10600         if (first.pr != NoProc) {
10601             ExitAnalyzeMode();
10602             DoSleep( appData.delayBeforeQuit );
10603             SendToProgram("quit\n", &first);
10604             DoSleep( appData.delayAfterQuit );
10605             DestroyChildProcess(first.pr, first.useSigterm);
10606         }
10607         first.pr = NoProc;
10608     }
10609     if (second.reuse) {
10610         /* Put second chess program into idle state */
10611         if (second.pr != NoProc &&
10612             gameMode == TwoMachinesPlay) {
10613             SendToProgram("force\n", &second);
10614             if (second.usePing) {
10615               char buf[MSG_SIZ];
10616               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10617               SendToProgram(buf, &second);
10618             }
10619         }
10620     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10621         /* Kill off second chess program */
10622         if (second.isr != NULL)
10623           RemoveInputSource(second.isr);
10624         second.isr = NULL;
10625
10626         if (second.pr != NoProc) {
10627             DoSleep( appData.delayBeforeQuit );
10628             SendToProgram("quit\n", &second);
10629             DoSleep( appData.delayAfterQuit );
10630             DestroyChildProcess(second.pr, second.useSigterm);
10631         }
10632         second.pr = NoProc;
10633     }
10634
10635     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10636         char resChar = '=';
10637         switch (result) {
10638         case WhiteWins:
10639           resChar = '+';
10640           if (first.twoMachinesColor[0] == 'w') {
10641             first.matchWins++;
10642           } else {
10643             second.matchWins++;
10644           }
10645           break;
10646         case BlackWins:
10647           resChar = '-';
10648           if (first.twoMachinesColor[0] == 'b') {
10649             first.matchWins++;
10650           } else {
10651             second.matchWins++;
10652           }
10653           break;
10654         case GameUnfinished:
10655           resChar = ' ';
10656         default:
10657           break;
10658         }
10659
10660         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10661         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10662             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10663             ReserveGame(nextGame, resChar); // sets nextGame
10664             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10665             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10666         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10667
10668         if (nextGame <= appData.matchGames && !abortMatch) {
10669             gameMode = nextGameMode;
10670             matchGame = nextGame; // this will be overruled in tourney mode!
10671             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10672             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10673             endingGame = 0; /* [HGM] crash */
10674             return;
10675         } else {
10676             gameMode = nextGameMode;
10677             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10678                      first.tidy, second.tidy,
10679                      first.matchWins, second.matchWins,
10680                      appData.matchGames - (first.matchWins + second.matchWins));
10681             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10682             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10683             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10684             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10685                 first.twoMachinesColor = "black\n";
10686                 second.twoMachinesColor = "white\n";
10687             } else {
10688                 first.twoMachinesColor = "white\n";
10689                 second.twoMachinesColor = "black\n";
10690             }
10691         }
10692     }
10693     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10694         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10695       ExitAnalyzeMode();
10696     gameMode = nextGameMode;
10697     ModeHighlight();
10698     endingGame = 0;  /* [HGM] crash */
10699     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10700         if(matchMode == TRUE) { // match through command line: exit with or without popup
10701             if(ranking) {
10702                 ToNrEvent(forwardMostMove);
10703                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10704                 else ExitEvent(0);
10705             } else DisplayFatalError(buf, 0, 0);
10706         } else { // match through menu; just stop, with or without popup
10707             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10708             ModeHighlight();
10709             if(ranking){
10710                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10711             } else DisplayNote(buf);
10712       }
10713       if(ranking) free(ranking);
10714     }
10715 }
10716
10717 /* Assumes program was just initialized (initString sent).
10718    Leaves program in force mode. */
10719 void
10720 FeedMovesToProgram (ChessProgramState *cps, int upto)
10721 {
10722     int i;
10723
10724     if (appData.debugMode)
10725       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10726               startedFromSetupPosition ? "position and " : "",
10727               backwardMostMove, upto, cps->which);
10728     if(currentlyInitializedVariant != gameInfo.variant) {
10729       char buf[MSG_SIZ];
10730         // [HGM] variantswitch: make engine aware of new variant
10731         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10732                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10733         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10734         SendToProgram(buf, cps);
10735         currentlyInitializedVariant = gameInfo.variant;
10736     }
10737     SendToProgram("force\n", cps);
10738     if (startedFromSetupPosition) {
10739         SendBoard(cps, backwardMostMove);
10740     if (appData.debugMode) {
10741         fprintf(debugFP, "feedMoves\n");
10742     }
10743     }
10744     for (i = backwardMostMove; i < upto; i++) {
10745         SendMoveToProgram(i, cps);
10746     }
10747 }
10748
10749
10750 int
10751 ResurrectChessProgram ()
10752 {
10753      /* The chess program may have exited.
10754         If so, restart it and feed it all the moves made so far. */
10755     static int doInit = 0;
10756
10757     if (appData.noChessProgram) return 1;
10758
10759     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10760         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10761         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10762         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10763     } else {
10764         if (first.pr != NoProc) return 1;
10765         StartChessProgram(&first);
10766     }
10767     InitChessProgram(&first, FALSE);
10768     FeedMovesToProgram(&first, currentMove);
10769
10770     if (!first.sendTime) {
10771         /* can't tell gnuchess what its clock should read,
10772            so we bow to its notion. */
10773         ResetClocks();
10774         timeRemaining[0][currentMove] = whiteTimeRemaining;
10775         timeRemaining[1][currentMove] = blackTimeRemaining;
10776     }
10777
10778     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10779                 appData.icsEngineAnalyze) && first.analysisSupport) {
10780       SendToProgram("analyze\n", &first);
10781       first.analyzing = TRUE;
10782     }
10783     return 1;
10784 }
10785
10786 /*
10787  * Button procedures
10788  */
10789 void
10790 Reset (int redraw, int init)
10791 {
10792     int i;
10793
10794     if (appData.debugMode) {
10795         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10796                 redraw, init, gameMode);
10797     }
10798     CleanupTail(); // [HGM] vari: delete any stored variations
10799     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10800     pausing = pauseExamInvalid = FALSE;
10801     startedFromSetupPosition = blackPlaysFirst = FALSE;
10802     firstMove = TRUE;
10803     whiteFlag = blackFlag = FALSE;
10804     userOfferedDraw = FALSE;
10805     hintRequested = bookRequested = FALSE;
10806     first.maybeThinking = FALSE;
10807     second.maybeThinking = FALSE;
10808     first.bookSuspend = FALSE; // [HGM] book
10809     second.bookSuspend = FALSE;
10810     thinkOutput[0] = NULLCHAR;
10811     lastHint[0] = NULLCHAR;
10812     ClearGameInfo(&gameInfo);
10813     gameInfo.variant = StringToVariant(appData.variant);
10814     ics_user_moved = ics_clock_paused = FALSE;
10815     ics_getting_history = H_FALSE;
10816     ics_gamenum = -1;
10817     white_holding[0] = black_holding[0] = NULLCHAR;
10818     ClearProgramStats();
10819     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10820
10821     ResetFrontEnd();
10822     ClearHighlights();
10823     flipView = appData.flipView;
10824     ClearPremoveHighlights();
10825     gotPremove = FALSE;
10826     alarmSounded = FALSE;
10827
10828     GameEnds(EndOfFile, NULL, GE_PLAYER);
10829     if(appData.serverMovesName != NULL) {
10830         /* [HGM] prepare to make moves file for broadcasting */
10831         clock_t t = clock();
10832         if(serverMoves != NULL) fclose(serverMoves);
10833         serverMoves = fopen(appData.serverMovesName, "r");
10834         if(serverMoves != NULL) {
10835             fclose(serverMoves);
10836             /* delay 15 sec before overwriting, so all clients can see end */
10837             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10838         }
10839         serverMoves = fopen(appData.serverMovesName, "w");
10840     }
10841
10842     ExitAnalyzeMode();
10843     gameMode = BeginningOfGame;
10844     ModeHighlight();
10845     if(appData.icsActive) gameInfo.variant = VariantNormal;
10846     currentMove = forwardMostMove = backwardMostMove = 0;
10847     MarkTargetSquares(1);
10848     InitPosition(redraw);
10849     for (i = 0; i < MAX_MOVES; i++) {
10850         if (commentList[i] != NULL) {
10851             free(commentList[i]);
10852             commentList[i] = NULL;
10853         }
10854     }
10855     ResetClocks();
10856     timeRemaining[0][0] = whiteTimeRemaining;
10857     timeRemaining[1][0] = blackTimeRemaining;
10858
10859     if (first.pr == NoProc) {
10860         StartChessProgram(&first);
10861     }
10862     if (init) {
10863             InitChessProgram(&first, startedFromSetupPosition);
10864     }
10865     DisplayTitle("");
10866     DisplayMessage("", "");
10867     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10868     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10869     ClearMap();        // [HGM] exclude: invalidate map
10870 }
10871
10872 void
10873 AutoPlayGameLoop ()
10874 {
10875     for (;;) {
10876         if (!AutoPlayOneMove())
10877           return;
10878         if (matchMode || appData.timeDelay == 0)
10879           continue;
10880         if (appData.timeDelay < 0)
10881           return;
10882         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10883         break;
10884     }
10885 }
10886
10887
10888 int
10889 AutoPlayOneMove ()
10890 {
10891     int fromX, fromY, toX, toY;
10892
10893     if (appData.debugMode) {
10894       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10895     }
10896
10897     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10898       return FALSE;
10899
10900     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10901       pvInfoList[currentMove].depth = programStats.depth;
10902       pvInfoList[currentMove].score = programStats.score;
10903       pvInfoList[currentMove].time  = 0;
10904       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10905     }
10906
10907     if (currentMove >= forwardMostMove) {
10908       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10909 //      gameMode = EndOfGame;
10910 //      ModeHighlight();
10911
10912       /* [AS] Clear current move marker at the end of a game */
10913       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10914
10915       return FALSE;
10916     }
10917
10918     toX = moveList[currentMove][2] - AAA;
10919     toY = moveList[currentMove][3] - ONE;
10920
10921     if (moveList[currentMove][1] == '@') {
10922         if (appData.highlightLastMove) {
10923             SetHighlights(-1, -1, toX, toY);
10924         }
10925     } else {
10926         fromX = moveList[currentMove][0] - AAA;
10927         fromY = moveList[currentMove][1] - ONE;
10928
10929         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10930
10931         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10932
10933         if (appData.highlightLastMove) {
10934             SetHighlights(fromX, fromY, toX, toY);
10935         }
10936     }
10937     DisplayMove(currentMove);
10938     SendMoveToProgram(currentMove++, &first);
10939     DisplayBothClocks();
10940     DrawPosition(FALSE, boards[currentMove]);
10941     // [HGM] PV info: always display, routine tests if empty
10942     DisplayComment(currentMove - 1, commentList[currentMove]);
10943     return TRUE;
10944 }
10945
10946
10947 int
10948 LoadGameOneMove (ChessMove readAhead)
10949 {
10950     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10951     char promoChar = NULLCHAR;
10952     ChessMove moveType;
10953     char move[MSG_SIZ];
10954     char *p, *q;
10955
10956     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10957         gameMode != AnalyzeMode && gameMode != Training) {
10958         gameFileFP = NULL;
10959         return FALSE;
10960     }
10961
10962     yyboardindex = forwardMostMove;
10963     if (readAhead != EndOfFile) {
10964       moveType = readAhead;
10965     } else {
10966       if (gameFileFP == NULL)
10967           return FALSE;
10968       moveType = (ChessMove) Myylex();
10969     }
10970
10971     done = FALSE;
10972     switch (moveType) {
10973       case Comment:
10974         if (appData.debugMode)
10975           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10976         p = yy_text;
10977
10978         /* append the comment but don't display it */
10979         AppendComment(currentMove, p, FALSE);
10980         return TRUE;
10981
10982       case WhiteCapturesEnPassant:
10983       case BlackCapturesEnPassant:
10984       case WhitePromotion:
10985       case BlackPromotion:
10986       case WhiteNonPromotion:
10987       case BlackNonPromotion:
10988       case NormalMove:
10989       case WhiteKingSideCastle:
10990       case WhiteQueenSideCastle:
10991       case BlackKingSideCastle:
10992       case BlackQueenSideCastle:
10993       case WhiteKingSideCastleWild:
10994       case WhiteQueenSideCastleWild:
10995       case BlackKingSideCastleWild:
10996       case BlackQueenSideCastleWild:
10997       /* PUSH Fabien */
10998       case WhiteHSideCastleFR:
10999       case WhiteASideCastleFR:
11000       case BlackHSideCastleFR:
11001       case BlackASideCastleFR:
11002       /* POP Fabien */
11003         if (appData.debugMode)
11004           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11005         fromX = currentMoveString[0] - AAA;
11006         fromY = currentMoveString[1] - ONE;
11007         toX = currentMoveString[2] - AAA;
11008         toY = currentMoveString[3] - ONE;
11009         promoChar = currentMoveString[4];
11010         break;
11011
11012       case WhiteDrop:
11013       case BlackDrop:
11014         if (appData.debugMode)
11015           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11016         fromX = moveType == WhiteDrop ?
11017           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11018         (int) CharToPiece(ToLower(currentMoveString[0]));
11019         fromY = DROP_RANK;
11020         toX = currentMoveString[2] - AAA;
11021         toY = currentMoveString[3] - ONE;
11022         break;
11023
11024       case WhiteWins:
11025       case BlackWins:
11026       case GameIsDrawn:
11027       case GameUnfinished:
11028         if (appData.debugMode)
11029           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11030         p = strchr(yy_text, '{');
11031         if (p == NULL) p = strchr(yy_text, '(');
11032         if (p == NULL) {
11033             p = yy_text;
11034             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11035         } else {
11036             q = strchr(p, *p == '{' ? '}' : ')');
11037             if (q != NULL) *q = NULLCHAR;
11038             p++;
11039         }
11040         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11041         GameEnds(moveType, p, GE_FILE);
11042         done = TRUE;
11043         if (cmailMsgLoaded) {
11044             ClearHighlights();
11045             flipView = WhiteOnMove(currentMove);
11046             if (moveType == GameUnfinished) flipView = !flipView;
11047             if (appData.debugMode)
11048               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11049         }
11050         break;
11051
11052       case EndOfFile:
11053         if (appData.debugMode)
11054           fprintf(debugFP, "Parser hit end of file\n");
11055         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11056           case MT_NONE:
11057           case MT_CHECK:
11058             break;
11059           case MT_CHECKMATE:
11060           case MT_STAINMATE:
11061             if (WhiteOnMove(currentMove)) {
11062                 GameEnds(BlackWins, "Black mates", GE_FILE);
11063             } else {
11064                 GameEnds(WhiteWins, "White mates", GE_FILE);
11065             }
11066             break;
11067           case MT_STALEMATE:
11068             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11069             break;
11070         }
11071         done = TRUE;
11072         break;
11073
11074       case MoveNumberOne:
11075         if (lastLoadGameStart == GNUChessGame) {
11076             /* GNUChessGames have numbers, but they aren't move numbers */
11077             if (appData.debugMode)
11078               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11079                       yy_text, (int) moveType);
11080             return LoadGameOneMove(EndOfFile); /* tail recursion */
11081         }
11082         /* else fall thru */
11083
11084       case XBoardGame:
11085       case GNUChessGame:
11086       case PGNTag:
11087         /* Reached start of next game in file */
11088         if (appData.debugMode)
11089           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11090         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11091           case MT_NONE:
11092           case MT_CHECK:
11093             break;
11094           case MT_CHECKMATE:
11095           case MT_STAINMATE:
11096             if (WhiteOnMove(currentMove)) {
11097                 GameEnds(BlackWins, "Black mates", GE_FILE);
11098             } else {
11099                 GameEnds(WhiteWins, "White mates", GE_FILE);
11100             }
11101             break;
11102           case MT_STALEMATE:
11103             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11104             break;
11105         }
11106         done = TRUE;
11107         break;
11108
11109       case PositionDiagram:     /* should not happen; ignore */
11110       case ElapsedTime:         /* ignore */
11111       case NAG:                 /* ignore */
11112         if (appData.debugMode)
11113           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11114                   yy_text, (int) moveType);
11115         return LoadGameOneMove(EndOfFile); /* tail recursion */
11116
11117       case IllegalMove:
11118         if (appData.testLegality) {
11119             if (appData.debugMode)
11120               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11121             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11122                     (forwardMostMove / 2) + 1,
11123                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11124             DisplayError(move, 0);
11125             done = TRUE;
11126         } else {
11127             if (appData.debugMode)
11128               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11129                       yy_text, currentMoveString);
11130             fromX = currentMoveString[0] - AAA;
11131             fromY = currentMoveString[1] - ONE;
11132             toX = currentMoveString[2] - AAA;
11133             toY = currentMoveString[3] - ONE;
11134             promoChar = currentMoveString[4];
11135         }
11136         break;
11137
11138       case AmbiguousMove:
11139         if (appData.debugMode)
11140           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11141         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11142                 (forwardMostMove / 2) + 1,
11143                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11144         DisplayError(move, 0);
11145         done = TRUE;
11146         break;
11147
11148       default:
11149       case ImpossibleMove:
11150         if (appData.debugMode)
11151           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11152         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11153                 (forwardMostMove / 2) + 1,
11154                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11155         DisplayError(move, 0);
11156         done = TRUE;
11157         break;
11158     }
11159
11160     if (done) {
11161         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11162             DrawPosition(FALSE, boards[currentMove]);
11163             DisplayBothClocks();
11164             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11165               DisplayComment(currentMove - 1, commentList[currentMove]);
11166         }
11167         (void) StopLoadGameTimer();
11168         gameFileFP = NULL;
11169         cmailOldMove = forwardMostMove;
11170         return FALSE;
11171     } else {
11172         /* currentMoveString is set as a side-effect of yylex */
11173
11174         thinkOutput[0] = NULLCHAR;
11175         MakeMove(fromX, fromY, toX, toY, promoChar);
11176         currentMove = forwardMostMove;
11177         return TRUE;
11178     }
11179 }
11180
11181 /* Load the nth game from the given file */
11182 int
11183 LoadGameFromFile (char *filename, int n, char *title, int useList)
11184 {
11185     FILE *f;
11186     char buf[MSG_SIZ];
11187
11188     if (strcmp(filename, "-") == 0) {
11189         f = stdin;
11190         title = "stdin";
11191     } else {
11192         f = fopen(filename, "rb");
11193         if (f == NULL) {
11194           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11195             DisplayError(buf, errno);
11196             return FALSE;
11197         }
11198     }
11199     if (fseek(f, 0, 0) == -1) {
11200         /* f is not seekable; probably a pipe */
11201         useList = FALSE;
11202     }
11203     if (useList && n == 0) {
11204         int error = GameListBuild(f);
11205         if (error) {
11206             DisplayError(_("Cannot build game list"), error);
11207         } else if (!ListEmpty(&gameList) &&
11208                    ((ListGame *) gameList.tailPred)->number > 1) {
11209             GameListPopUp(f, title);
11210             return TRUE;
11211         }
11212         GameListDestroy();
11213         n = 1;
11214     }
11215     if (n == 0) n = 1;
11216     return LoadGame(f, n, title, FALSE);
11217 }
11218
11219
11220 void
11221 MakeRegisteredMove ()
11222 {
11223     int fromX, fromY, toX, toY;
11224     char promoChar;
11225     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11226         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11227           case CMAIL_MOVE:
11228           case CMAIL_DRAW:
11229             if (appData.debugMode)
11230               fprintf(debugFP, "Restoring %s for game %d\n",
11231                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11232
11233             thinkOutput[0] = NULLCHAR;
11234             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11235             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11236             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11237             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11238             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11239             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11240             MakeMove(fromX, fromY, toX, toY, promoChar);
11241             ShowMove(fromX, fromY, toX, toY);
11242
11243             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11244               case MT_NONE:
11245               case MT_CHECK:
11246                 break;
11247
11248               case MT_CHECKMATE:
11249               case MT_STAINMATE:
11250                 if (WhiteOnMove(currentMove)) {
11251                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11252                 } else {
11253                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11254                 }
11255                 break;
11256
11257               case MT_STALEMATE:
11258                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11259                 break;
11260             }
11261
11262             break;
11263
11264           case CMAIL_RESIGN:
11265             if (WhiteOnMove(currentMove)) {
11266                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11267             } else {
11268                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11269             }
11270             break;
11271
11272           case CMAIL_ACCEPT:
11273             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11274             break;
11275
11276           default:
11277             break;
11278         }
11279     }
11280
11281     return;
11282 }
11283
11284 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11285 int
11286 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11287 {
11288     int retVal;
11289
11290     if (gameNumber > nCmailGames) {
11291         DisplayError(_("No more games in this message"), 0);
11292         return FALSE;
11293     }
11294     if (f == lastLoadGameFP) {
11295         int offset = gameNumber - lastLoadGameNumber;
11296         if (offset == 0) {
11297             cmailMsg[0] = NULLCHAR;
11298             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11299                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11300                 nCmailMovesRegistered--;
11301             }
11302             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11303             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11304                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11305             }
11306         } else {
11307             if (! RegisterMove()) return FALSE;
11308         }
11309     }
11310
11311     retVal = LoadGame(f, gameNumber, title, useList);
11312
11313     /* Make move registered during previous look at this game, if any */
11314     MakeRegisteredMove();
11315
11316     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11317         commentList[currentMove]
11318           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11319         DisplayComment(currentMove - 1, commentList[currentMove]);
11320     }
11321
11322     return retVal;
11323 }
11324
11325 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11326 int
11327 ReloadGame (int offset)
11328 {
11329     int gameNumber = lastLoadGameNumber + offset;
11330     if (lastLoadGameFP == NULL) {
11331         DisplayError(_("No game has been loaded yet"), 0);
11332         return FALSE;
11333     }
11334     if (gameNumber <= 0) {
11335         DisplayError(_("Can't back up any further"), 0);
11336         return FALSE;
11337     }
11338     if (cmailMsgLoaded) {
11339         return CmailLoadGame(lastLoadGameFP, gameNumber,
11340                              lastLoadGameTitle, lastLoadGameUseList);
11341     } else {
11342         return LoadGame(lastLoadGameFP, gameNumber,
11343                         lastLoadGameTitle, lastLoadGameUseList);
11344     }
11345 }
11346
11347 int keys[EmptySquare+1];
11348
11349 int
11350 PositionMatches (Board b1, Board b2)
11351 {
11352     int r, f, sum=0;
11353     switch(appData.searchMode) {
11354         case 1: return CompareWithRights(b1, b2);
11355         case 2:
11356             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11357                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11358             }
11359             return TRUE;
11360         case 3:
11361             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11362               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11363                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11364             }
11365             return sum==0;
11366         case 4:
11367             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11368                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11369             }
11370             return sum==0;
11371     }
11372     return TRUE;
11373 }
11374
11375 #define Q_PROMO  4
11376 #define Q_EP     3
11377 #define Q_BCASTL 2
11378 #define Q_WCASTL 1
11379
11380 int pieceList[256], quickBoard[256];
11381 ChessSquare pieceType[256] = { EmptySquare };
11382 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11383 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11384 int soughtTotal, turn;
11385 Boolean epOK, flipSearch;
11386
11387 typedef struct {
11388     unsigned char piece, to;
11389 } Move;
11390
11391 #define DSIZE (250000)
11392
11393 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11394 Move *moveDatabase = initialSpace;
11395 unsigned int movePtr, dataSize = DSIZE;
11396
11397 int
11398 MakePieceList (Board board, int *counts)
11399 {
11400     int r, f, n=Q_PROMO, total=0;
11401     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11402     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11403         int sq = f + (r<<4);
11404         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11405             quickBoard[sq] = ++n;
11406             pieceList[n] = sq;
11407             pieceType[n] = board[r][f];
11408             counts[board[r][f]]++;
11409             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11410             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11411             total++;
11412         }
11413     }
11414     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11415     return total;
11416 }
11417
11418 void
11419 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11420 {
11421     int sq = fromX + (fromY<<4);
11422     int piece = quickBoard[sq];
11423     quickBoard[sq] = 0;
11424     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11425     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11426         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11427         moveDatabase[movePtr++].piece = Q_WCASTL;
11428         quickBoard[sq] = piece;
11429         piece = quickBoard[from]; quickBoard[from] = 0;
11430         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11431     } else
11432     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11433         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11434         moveDatabase[movePtr++].piece = Q_BCASTL;
11435         quickBoard[sq] = piece;
11436         piece = quickBoard[from]; quickBoard[from] = 0;
11437         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11438     } else
11439     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11440         quickBoard[(fromY<<4)+toX] = 0;
11441         moveDatabase[movePtr].piece = Q_EP;
11442         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11443         moveDatabase[movePtr].to = sq;
11444     } else
11445     if(promoPiece != pieceType[piece]) {
11446         moveDatabase[movePtr++].piece = Q_PROMO;
11447         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11448     }
11449     moveDatabase[movePtr].piece = piece;
11450     quickBoard[sq] = piece;
11451     movePtr++;
11452 }
11453
11454 int
11455 PackGame (Board board)
11456 {
11457     Move *newSpace = NULL;
11458     moveDatabase[movePtr].piece = 0; // terminate previous game
11459     if(movePtr > dataSize) {
11460         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11461         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11462         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11463         if(newSpace) {
11464             int i;
11465             Move *p = moveDatabase, *q = newSpace;
11466             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11467             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11468             moveDatabase = newSpace;
11469         } else { // calloc failed, we must be out of memory. Too bad...
11470             dataSize = 0; // prevent calloc events for all subsequent games
11471             return 0;     // and signal this one isn't cached
11472         }
11473     }
11474     movePtr++;
11475     MakePieceList(board, counts);
11476     return movePtr;
11477 }
11478
11479 int
11480 QuickCompare (Board board, int *minCounts, int *maxCounts)
11481 {   // compare according to search mode
11482     int r, f;
11483     switch(appData.searchMode)
11484     {
11485       case 1: // exact position match
11486         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11487         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11488             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11489         }
11490         break;
11491       case 2: // can have extra material on empty squares
11492         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11493             if(board[r][f] == EmptySquare) continue;
11494             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11495         }
11496         break;
11497       case 3: // material with exact Pawn structure
11498         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11499             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11500             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11501         } // fall through to material comparison
11502       case 4: // exact material
11503         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11504         break;
11505       case 6: // material range with given imbalance
11506         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11507         // fall through to range comparison
11508       case 5: // material range
11509         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11510     }
11511     return TRUE;
11512 }
11513
11514 int
11515 QuickScan (Board board, Move *move)
11516 {   // reconstruct game,and compare all positions in it
11517     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11518     do {
11519         int piece = move->piece;
11520         int to = move->to, from = pieceList[piece];
11521         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11522           if(!piece) return -1;
11523           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11524             piece = (++move)->piece;
11525             from = pieceList[piece];
11526             counts[pieceType[piece]]--;
11527             pieceType[piece] = (ChessSquare) move->to;
11528             counts[move->to]++;
11529           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11530             counts[pieceType[quickBoard[to]]]--;
11531             quickBoard[to] = 0; total--;
11532             move++;
11533             continue;
11534           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11535             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11536             from  = pieceList[piece]; // so this must be King
11537             quickBoard[from] = 0;
11538             quickBoard[to] = piece;
11539             pieceList[piece] = to;
11540             move++;
11541             continue;
11542           }
11543         }
11544         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11545         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11546         quickBoard[from] = 0;
11547         quickBoard[to] = piece;
11548         pieceList[piece] = to;
11549         cnt++; turn ^= 3;
11550         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11551            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11552            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11553                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11554           ) {
11555             static int lastCounts[EmptySquare+1];
11556             int i;
11557             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11558             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11559         } else stretch = 0;
11560         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11561         move++;
11562     } while(1);
11563 }
11564
11565 void
11566 InitSearch ()
11567 {
11568     int r, f;
11569     flipSearch = FALSE;
11570     CopyBoard(soughtBoard, boards[currentMove]);
11571     soughtTotal = MakePieceList(soughtBoard, maxSought);
11572     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11573     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11574     CopyBoard(reverseBoard, boards[currentMove]);
11575     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11576         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11577         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11578         reverseBoard[r][f] = piece;
11579     }
11580     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11581     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11582     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11583                  || (boards[currentMove][CASTLING][2] == NoRights || 
11584                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11585                  && (boards[currentMove][CASTLING][5] == NoRights || 
11586                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11587       ) {
11588         flipSearch = TRUE;
11589         CopyBoard(flipBoard, soughtBoard);
11590         CopyBoard(rotateBoard, reverseBoard);
11591         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11592             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11593             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11594         }
11595     }
11596     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11597     if(appData.searchMode >= 5) {
11598         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11599         MakePieceList(soughtBoard, minSought);
11600         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11601     }
11602     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11603         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11604 }
11605
11606 GameInfo dummyInfo;
11607
11608 int
11609 GameContainsPosition (FILE *f, ListGame *lg)
11610 {
11611     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11612     int fromX, fromY, toX, toY;
11613     char promoChar;
11614     static int initDone=FALSE;
11615
11616     // weed out games based on numerical tag comparison
11617     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11618     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11619     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11620     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11621     if(!initDone) {
11622         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11623         initDone = TRUE;
11624     }
11625     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11626     else CopyBoard(boards[scratch], initialPosition); // default start position
11627     if(lg->moves) {
11628         turn = btm + 1;
11629         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11630         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11631     }
11632     if(btm) plyNr++;
11633     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11634     fseek(f, lg->offset, 0);
11635     yynewfile(f);
11636     while(1) {
11637         yyboardindex = scratch;
11638         quickFlag = plyNr+1;
11639         next = Myylex();
11640         quickFlag = 0;
11641         switch(next) {
11642             case PGNTag:
11643                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11644             default:
11645                 continue;
11646
11647             case XBoardGame:
11648             case GNUChessGame:
11649                 if(plyNr) return -1; // after we have seen moves, this is for new game
11650               continue;
11651
11652             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11653             case ImpossibleMove:
11654             case WhiteWins: // game ends here with these four
11655             case BlackWins:
11656             case GameIsDrawn:
11657             case GameUnfinished:
11658                 return -1;
11659
11660             case IllegalMove:
11661                 if(appData.testLegality) return -1;
11662             case WhiteCapturesEnPassant:
11663             case BlackCapturesEnPassant:
11664             case WhitePromotion:
11665             case BlackPromotion:
11666             case WhiteNonPromotion:
11667             case BlackNonPromotion:
11668             case NormalMove:
11669             case WhiteKingSideCastle:
11670             case WhiteQueenSideCastle:
11671             case BlackKingSideCastle:
11672             case BlackQueenSideCastle:
11673             case WhiteKingSideCastleWild:
11674             case WhiteQueenSideCastleWild:
11675             case BlackKingSideCastleWild:
11676             case BlackQueenSideCastleWild:
11677             case WhiteHSideCastleFR:
11678             case WhiteASideCastleFR:
11679             case BlackHSideCastleFR:
11680             case BlackASideCastleFR:
11681                 fromX = currentMoveString[0] - AAA;
11682                 fromY = currentMoveString[1] - ONE;
11683                 toX = currentMoveString[2] - AAA;
11684                 toY = currentMoveString[3] - ONE;
11685                 promoChar = currentMoveString[4];
11686                 break;
11687             case WhiteDrop:
11688             case BlackDrop:
11689                 fromX = next == WhiteDrop ?
11690                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11691                   (int) CharToPiece(ToLower(currentMoveString[0]));
11692                 fromY = DROP_RANK;
11693                 toX = currentMoveString[2] - AAA;
11694                 toY = currentMoveString[3] - ONE;
11695                 promoChar = 0;
11696                 break;
11697         }
11698         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11699         plyNr++;
11700         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11701         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11702         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11703         if(appData.findMirror) {
11704             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11705             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11706         }
11707     }
11708 }
11709
11710 /* Load the nth game from open file f */
11711 int
11712 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11713 {
11714     ChessMove cm;
11715     char buf[MSG_SIZ];
11716     int gn = gameNumber;
11717     ListGame *lg = NULL;
11718     int numPGNTags = 0;
11719     int err, pos = -1;
11720     GameMode oldGameMode;
11721     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11722
11723     if (appData.debugMode)
11724         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11725
11726     if (gameMode == Training )
11727         SetTrainingModeOff();
11728
11729     oldGameMode = gameMode;
11730     if (gameMode != BeginningOfGame) {
11731       Reset(FALSE, TRUE);
11732     }
11733
11734     gameFileFP = f;
11735     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11736         fclose(lastLoadGameFP);
11737     }
11738
11739     if (useList) {
11740         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11741
11742         if (lg) {
11743             fseek(f, lg->offset, 0);
11744             GameListHighlight(gameNumber);
11745             pos = lg->position;
11746             gn = 1;
11747         }
11748         else {
11749             DisplayError(_("Game number out of range"), 0);
11750             return FALSE;
11751         }
11752     } else {
11753         GameListDestroy();
11754         if (fseek(f, 0, 0) == -1) {
11755             if (f == lastLoadGameFP ?
11756                 gameNumber == lastLoadGameNumber + 1 :
11757                 gameNumber == 1) {
11758                 gn = 1;
11759             } else {
11760                 DisplayError(_("Can't seek on game file"), 0);
11761                 return FALSE;
11762             }
11763         }
11764     }
11765     lastLoadGameFP = f;
11766     lastLoadGameNumber = gameNumber;
11767     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11768     lastLoadGameUseList = useList;
11769
11770     yynewfile(f);
11771
11772     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11773       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11774                 lg->gameInfo.black);
11775             DisplayTitle(buf);
11776     } else if (*title != NULLCHAR) {
11777         if (gameNumber > 1) {
11778           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11779             DisplayTitle(buf);
11780         } else {
11781             DisplayTitle(title);
11782         }
11783     }
11784
11785     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11786         gameMode = PlayFromGameFile;
11787         ModeHighlight();
11788     }
11789
11790     currentMove = forwardMostMove = backwardMostMove = 0;
11791     CopyBoard(boards[0], initialPosition);
11792     StopClocks();
11793
11794     /*
11795      * Skip the first gn-1 games in the file.
11796      * Also skip over anything that precedes an identifiable
11797      * start of game marker, to avoid being confused by
11798      * garbage at the start of the file.  Currently
11799      * recognized start of game markers are the move number "1",
11800      * the pattern "gnuchess .* game", the pattern
11801      * "^[#;%] [^ ]* game file", and a PGN tag block.
11802      * A game that starts with one of the latter two patterns
11803      * will also have a move number 1, possibly
11804      * following a position diagram.
11805      * 5-4-02: Let's try being more lenient and allowing a game to
11806      * start with an unnumbered move.  Does that break anything?
11807      */
11808     cm = lastLoadGameStart = EndOfFile;
11809     while (gn > 0) {
11810         yyboardindex = forwardMostMove;
11811         cm = (ChessMove) Myylex();
11812         switch (cm) {
11813           case EndOfFile:
11814             if (cmailMsgLoaded) {
11815                 nCmailGames = CMAIL_MAX_GAMES - gn;
11816             } else {
11817                 Reset(TRUE, TRUE);
11818                 DisplayError(_("Game not found in file"), 0);
11819             }
11820             return FALSE;
11821
11822           case GNUChessGame:
11823           case XBoardGame:
11824             gn--;
11825             lastLoadGameStart = cm;
11826             break;
11827
11828           case MoveNumberOne:
11829             switch (lastLoadGameStart) {
11830               case GNUChessGame:
11831               case XBoardGame:
11832               case PGNTag:
11833                 break;
11834               case MoveNumberOne:
11835               case EndOfFile:
11836                 gn--;           /* count this game */
11837                 lastLoadGameStart = cm;
11838                 break;
11839               default:
11840                 /* impossible */
11841                 break;
11842             }
11843             break;
11844
11845           case PGNTag:
11846             switch (lastLoadGameStart) {
11847               case GNUChessGame:
11848               case PGNTag:
11849               case MoveNumberOne:
11850               case EndOfFile:
11851                 gn--;           /* count this game */
11852                 lastLoadGameStart = cm;
11853                 break;
11854               case XBoardGame:
11855                 lastLoadGameStart = cm; /* game counted already */
11856                 break;
11857               default:
11858                 /* impossible */
11859                 break;
11860             }
11861             if (gn > 0) {
11862                 do {
11863                     yyboardindex = forwardMostMove;
11864                     cm = (ChessMove) Myylex();
11865                 } while (cm == PGNTag || cm == Comment);
11866             }
11867             break;
11868
11869           case WhiteWins:
11870           case BlackWins:
11871           case GameIsDrawn:
11872             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11873                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11874                     != CMAIL_OLD_RESULT) {
11875                     nCmailResults ++ ;
11876                     cmailResult[  CMAIL_MAX_GAMES
11877                                 - gn - 1] = CMAIL_OLD_RESULT;
11878                 }
11879             }
11880             break;
11881
11882           case NormalMove:
11883             /* Only a NormalMove can be at the start of a game
11884              * without a position diagram. */
11885             if (lastLoadGameStart == EndOfFile ) {
11886               gn--;
11887               lastLoadGameStart = MoveNumberOne;
11888             }
11889             break;
11890
11891           default:
11892             break;
11893         }
11894     }
11895
11896     if (appData.debugMode)
11897       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11898
11899     if (cm == XBoardGame) {
11900         /* Skip any header junk before position diagram and/or move 1 */
11901         for (;;) {
11902             yyboardindex = forwardMostMove;
11903             cm = (ChessMove) Myylex();
11904
11905             if (cm == EndOfFile ||
11906                 cm == GNUChessGame || cm == XBoardGame) {
11907                 /* Empty game; pretend end-of-file and handle later */
11908                 cm = EndOfFile;
11909                 break;
11910             }
11911
11912             if (cm == MoveNumberOne || cm == PositionDiagram ||
11913                 cm == PGNTag || cm == Comment)
11914               break;
11915         }
11916     } else if (cm == GNUChessGame) {
11917         if (gameInfo.event != NULL) {
11918             free(gameInfo.event);
11919         }
11920         gameInfo.event = StrSave(yy_text);
11921     }
11922
11923     startedFromSetupPosition = FALSE;
11924     while (cm == PGNTag) {
11925         if (appData.debugMode)
11926           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11927         err = ParsePGNTag(yy_text, &gameInfo);
11928         if (!err) numPGNTags++;
11929
11930         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11931         if(gameInfo.variant != oldVariant) {
11932             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11933             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11934             InitPosition(TRUE);
11935             oldVariant = gameInfo.variant;
11936             if (appData.debugMode)
11937               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11938         }
11939
11940
11941         if (gameInfo.fen != NULL) {
11942           Board initial_position;
11943           startedFromSetupPosition = TRUE;
11944           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11945             Reset(TRUE, TRUE);
11946             DisplayError(_("Bad FEN position in file"), 0);
11947             return FALSE;
11948           }
11949           CopyBoard(boards[0], initial_position);
11950           if (blackPlaysFirst) {
11951             currentMove = forwardMostMove = backwardMostMove = 1;
11952             CopyBoard(boards[1], initial_position);
11953             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11954             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11955             timeRemaining[0][1] = whiteTimeRemaining;
11956             timeRemaining[1][1] = blackTimeRemaining;
11957             if (commentList[0] != NULL) {
11958               commentList[1] = commentList[0];
11959               commentList[0] = NULL;
11960             }
11961           } else {
11962             currentMove = forwardMostMove = backwardMostMove = 0;
11963           }
11964           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11965           {   int i;
11966               initialRulePlies = FENrulePlies;
11967               for( i=0; i< nrCastlingRights; i++ )
11968                   initialRights[i] = initial_position[CASTLING][i];
11969           }
11970           yyboardindex = forwardMostMove;
11971           free(gameInfo.fen);
11972           gameInfo.fen = NULL;
11973         }
11974
11975         yyboardindex = forwardMostMove;
11976         cm = (ChessMove) Myylex();
11977
11978         /* Handle comments interspersed among the tags */
11979         while (cm == Comment) {
11980             char *p;
11981             if (appData.debugMode)
11982               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11983             p = yy_text;
11984             AppendComment(currentMove, p, FALSE);
11985             yyboardindex = forwardMostMove;
11986             cm = (ChessMove) Myylex();
11987         }
11988     }
11989
11990     /* don't rely on existence of Event tag since if game was
11991      * pasted from clipboard the Event tag may not exist
11992      */
11993     if (numPGNTags > 0){
11994         char *tags;
11995         if (gameInfo.variant == VariantNormal) {
11996           VariantClass v = StringToVariant(gameInfo.event);
11997           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11998           if(v < VariantShogi) gameInfo.variant = v;
11999         }
12000         if (!matchMode) {
12001           if( appData.autoDisplayTags ) {
12002             tags = PGNTags(&gameInfo);
12003             TagsPopUp(tags, CmailMsg());
12004             free(tags);
12005           }
12006         }
12007     } else {
12008         /* Make something up, but don't display it now */
12009         SetGameInfo();
12010         TagsPopDown();
12011     }
12012
12013     if (cm == PositionDiagram) {
12014         int i, j;
12015         char *p;
12016         Board initial_position;
12017
12018         if (appData.debugMode)
12019           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12020
12021         if (!startedFromSetupPosition) {
12022             p = yy_text;
12023             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12024               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12025                 switch (*p) {
12026                   case '{':
12027                   case '[':
12028                   case '-':
12029                   case ' ':
12030                   case '\t':
12031                   case '\n':
12032                   case '\r':
12033                     break;
12034                   default:
12035                     initial_position[i][j++] = CharToPiece(*p);
12036                     break;
12037                 }
12038             while (*p == ' ' || *p == '\t' ||
12039                    *p == '\n' || *p == '\r') p++;
12040
12041             if (strncmp(p, "black", strlen("black"))==0)
12042               blackPlaysFirst = TRUE;
12043             else
12044               blackPlaysFirst = FALSE;
12045             startedFromSetupPosition = TRUE;
12046
12047             CopyBoard(boards[0], initial_position);
12048             if (blackPlaysFirst) {
12049                 currentMove = forwardMostMove = backwardMostMove = 1;
12050                 CopyBoard(boards[1], initial_position);
12051                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12052                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12053                 timeRemaining[0][1] = whiteTimeRemaining;
12054                 timeRemaining[1][1] = blackTimeRemaining;
12055                 if (commentList[0] != NULL) {
12056                     commentList[1] = commentList[0];
12057                     commentList[0] = NULL;
12058                 }
12059             } else {
12060                 currentMove = forwardMostMove = backwardMostMove = 0;
12061             }
12062         }
12063         yyboardindex = forwardMostMove;
12064         cm = (ChessMove) Myylex();
12065     }
12066
12067     if (first.pr == NoProc) {
12068         StartChessProgram(&first);
12069     }
12070     InitChessProgram(&first, FALSE);
12071     SendToProgram("force\n", &first);
12072     if (startedFromSetupPosition) {
12073         SendBoard(&first, forwardMostMove);
12074     if (appData.debugMode) {
12075         fprintf(debugFP, "Load Game\n");
12076     }
12077         DisplayBothClocks();
12078     }
12079
12080     /* [HGM] server: flag to write setup moves in broadcast file as one */
12081     loadFlag = appData.suppressLoadMoves;
12082
12083     while (cm == Comment) {
12084         char *p;
12085         if (appData.debugMode)
12086           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12087         p = yy_text;
12088         AppendComment(currentMove, p, FALSE);
12089         yyboardindex = forwardMostMove;
12090         cm = (ChessMove) Myylex();
12091     }
12092
12093     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12094         cm == WhiteWins || cm == BlackWins ||
12095         cm == GameIsDrawn || cm == GameUnfinished) {
12096         DisplayMessage("", _("No moves in game"));
12097         if (cmailMsgLoaded) {
12098             if (appData.debugMode)
12099               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12100             ClearHighlights();
12101             flipView = FALSE;
12102         }
12103         DrawPosition(FALSE, boards[currentMove]);
12104         DisplayBothClocks();
12105         gameMode = EditGame;
12106         ModeHighlight();
12107         gameFileFP = NULL;
12108         cmailOldMove = 0;
12109         return TRUE;
12110     }
12111
12112     // [HGM] PV info: routine tests if comment empty
12113     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12114         DisplayComment(currentMove - 1, commentList[currentMove]);
12115     }
12116     if (!matchMode && appData.timeDelay != 0)
12117       DrawPosition(FALSE, boards[currentMove]);
12118
12119     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12120       programStats.ok_to_send = 1;
12121     }
12122
12123     /* if the first token after the PGN tags is a move
12124      * and not move number 1, retrieve it from the parser
12125      */
12126     if (cm != MoveNumberOne)
12127         LoadGameOneMove(cm);
12128
12129     /* load the remaining moves from the file */
12130     while (LoadGameOneMove(EndOfFile)) {
12131       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12132       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12133     }
12134
12135     /* rewind to the start of the game */
12136     currentMove = backwardMostMove;
12137
12138     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12139
12140     if (oldGameMode == AnalyzeFile ||
12141         oldGameMode == AnalyzeMode) {
12142       AnalyzeFileEvent();
12143     }
12144
12145     if (!matchMode && pos >= 0) {
12146         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12147     } else
12148     if (matchMode || appData.timeDelay == 0) {
12149       ToEndEvent();
12150     } else if (appData.timeDelay > 0) {
12151       AutoPlayGameLoop();
12152     }
12153
12154     if (appData.debugMode)
12155         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12156
12157     loadFlag = 0; /* [HGM] true game starts */
12158     return TRUE;
12159 }
12160
12161 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12162 int
12163 ReloadPosition (int offset)
12164 {
12165     int positionNumber = lastLoadPositionNumber + offset;
12166     if (lastLoadPositionFP == NULL) {
12167         DisplayError(_("No position has been loaded yet"), 0);
12168         return FALSE;
12169     }
12170     if (positionNumber <= 0) {
12171         DisplayError(_("Can't back up any further"), 0);
12172         return FALSE;
12173     }
12174     return LoadPosition(lastLoadPositionFP, positionNumber,
12175                         lastLoadPositionTitle);
12176 }
12177
12178 /* Load the nth position from the given file */
12179 int
12180 LoadPositionFromFile (char *filename, int n, char *title)
12181 {
12182     FILE *f;
12183     char buf[MSG_SIZ];
12184
12185     if (strcmp(filename, "-") == 0) {
12186         return LoadPosition(stdin, n, "stdin");
12187     } else {
12188         f = fopen(filename, "rb");
12189         if (f == NULL) {
12190             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12191             DisplayError(buf, errno);
12192             return FALSE;
12193         } else {
12194             return LoadPosition(f, n, title);
12195         }
12196     }
12197 }
12198
12199 /* Load the nth position from the given open file, and close it */
12200 int
12201 LoadPosition (FILE *f, int positionNumber, char *title)
12202 {
12203     char *p, line[MSG_SIZ];
12204     Board initial_position;
12205     int i, j, fenMode, pn;
12206
12207     if (gameMode == Training )
12208         SetTrainingModeOff();
12209
12210     if (gameMode != BeginningOfGame) {
12211         Reset(FALSE, TRUE);
12212     }
12213     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12214         fclose(lastLoadPositionFP);
12215     }
12216     if (positionNumber == 0) positionNumber = 1;
12217     lastLoadPositionFP = f;
12218     lastLoadPositionNumber = positionNumber;
12219     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12220     if (first.pr == NoProc && !appData.noChessProgram) {
12221       StartChessProgram(&first);
12222       InitChessProgram(&first, FALSE);
12223     }
12224     pn = positionNumber;
12225     if (positionNumber < 0) {
12226         /* Negative position number means to seek to that byte offset */
12227         if (fseek(f, -positionNumber, 0) == -1) {
12228             DisplayError(_("Can't seek on position file"), 0);
12229             return FALSE;
12230         };
12231         pn = 1;
12232     } else {
12233         if (fseek(f, 0, 0) == -1) {
12234             if (f == lastLoadPositionFP ?
12235                 positionNumber == lastLoadPositionNumber + 1 :
12236                 positionNumber == 1) {
12237                 pn = 1;
12238             } else {
12239                 DisplayError(_("Can't seek on position file"), 0);
12240                 return FALSE;
12241             }
12242         }
12243     }
12244     /* See if this file is FEN or old-style xboard */
12245     if (fgets(line, MSG_SIZ, f) == NULL) {
12246         DisplayError(_("Position not found in file"), 0);
12247         return FALSE;
12248     }
12249     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12250     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12251
12252     if (pn >= 2) {
12253         if (fenMode || line[0] == '#') pn--;
12254         while (pn > 0) {
12255             /* skip positions before number pn */
12256             if (fgets(line, MSG_SIZ, f) == NULL) {
12257                 Reset(TRUE, TRUE);
12258                 DisplayError(_("Position not found in file"), 0);
12259                 return FALSE;
12260             }
12261             if (fenMode || line[0] == '#') pn--;
12262         }
12263     }
12264
12265     if (fenMode) {
12266         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12267             DisplayError(_("Bad FEN position in file"), 0);
12268             return FALSE;
12269         }
12270     } else {
12271         (void) fgets(line, MSG_SIZ, f);
12272         (void) fgets(line, MSG_SIZ, f);
12273
12274         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12275             (void) fgets(line, MSG_SIZ, f);
12276             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12277                 if (*p == ' ')
12278                   continue;
12279                 initial_position[i][j++] = CharToPiece(*p);
12280             }
12281         }
12282
12283         blackPlaysFirst = FALSE;
12284         if (!feof(f)) {
12285             (void) fgets(line, MSG_SIZ, f);
12286             if (strncmp(line, "black", strlen("black"))==0)
12287               blackPlaysFirst = TRUE;
12288         }
12289     }
12290     startedFromSetupPosition = TRUE;
12291
12292     CopyBoard(boards[0], initial_position);
12293     if (blackPlaysFirst) {
12294         currentMove = forwardMostMove = backwardMostMove = 1;
12295         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12296         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12297         CopyBoard(boards[1], initial_position);
12298         DisplayMessage("", _("Black to play"));
12299     } else {
12300         currentMove = forwardMostMove = backwardMostMove = 0;
12301         DisplayMessage("", _("White to play"));
12302     }
12303     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12304     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12305         SendToProgram("force\n", &first);
12306         SendBoard(&first, forwardMostMove);
12307     }
12308     if (appData.debugMode) {
12309 int i, j;
12310   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12311   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12312         fprintf(debugFP, "Load Position\n");
12313     }
12314
12315     if (positionNumber > 1) {
12316       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12317         DisplayTitle(line);
12318     } else {
12319         DisplayTitle(title);
12320     }
12321     gameMode = EditGame;
12322     ModeHighlight();
12323     ResetClocks();
12324     timeRemaining[0][1] = whiteTimeRemaining;
12325     timeRemaining[1][1] = blackTimeRemaining;
12326     DrawPosition(FALSE, boards[currentMove]);
12327
12328     return TRUE;
12329 }
12330
12331
12332 void
12333 CopyPlayerNameIntoFileName (char **dest, char *src)
12334 {
12335     while (*src != NULLCHAR && *src != ',') {
12336         if (*src == ' ') {
12337             *(*dest)++ = '_';
12338             src++;
12339         } else {
12340             *(*dest)++ = *src++;
12341         }
12342     }
12343 }
12344
12345 char *
12346 DefaultFileName (char *ext)
12347 {
12348     static char def[MSG_SIZ];
12349     char *p;
12350
12351     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12352         p = def;
12353         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12354         *p++ = '-';
12355         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12356         *p++ = '.';
12357         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12358     } else {
12359         def[0] = NULLCHAR;
12360     }
12361     return def;
12362 }
12363
12364 /* Save the current game to the given file */
12365 int
12366 SaveGameToFile (char *filename, int append)
12367 {
12368     FILE *f;
12369     char buf[MSG_SIZ];
12370     int result, i, t,tot=0;
12371
12372     if (strcmp(filename, "-") == 0) {
12373         return SaveGame(stdout, 0, NULL);
12374     } else {
12375         for(i=0; i<10; i++) { // upto 10 tries
12376              f = fopen(filename, append ? "a" : "w");
12377              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12378              if(f || errno != 13) break;
12379              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12380              tot += t;
12381         }
12382         if (f == NULL) {
12383             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12384             DisplayError(buf, errno);
12385             return FALSE;
12386         } else {
12387             safeStrCpy(buf, lastMsg, MSG_SIZ);
12388             DisplayMessage(_("Waiting for access to save file"), "");
12389             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12390             DisplayMessage(_("Saving game"), "");
12391             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12392             result = SaveGame(f, 0, NULL);
12393             DisplayMessage(buf, "");
12394             return result;
12395         }
12396     }
12397 }
12398
12399 char *
12400 SavePart (char *str)
12401 {
12402     static char buf[MSG_SIZ];
12403     char *p;
12404
12405     p = strchr(str, ' ');
12406     if (p == NULL) return str;
12407     strncpy(buf, str, p - str);
12408     buf[p - str] = NULLCHAR;
12409     return buf;
12410 }
12411
12412 #define PGN_MAX_LINE 75
12413
12414 #define PGN_SIDE_WHITE  0
12415 #define PGN_SIDE_BLACK  1
12416
12417 static int
12418 FindFirstMoveOutOfBook (int side)
12419 {
12420     int result = -1;
12421
12422     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12423         int index = backwardMostMove;
12424         int has_book_hit = 0;
12425
12426         if( (index % 2) != side ) {
12427             index++;
12428         }
12429
12430         while( index < forwardMostMove ) {
12431             /* Check to see if engine is in book */
12432             int depth = pvInfoList[index].depth;
12433             int score = pvInfoList[index].score;
12434             int in_book = 0;
12435
12436             if( depth <= 2 ) {
12437                 in_book = 1;
12438             }
12439             else if( score == 0 && depth == 63 ) {
12440                 in_book = 1; /* Zappa */
12441             }
12442             else if( score == 2 && depth == 99 ) {
12443                 in_book = 1; /* Abrok */
12444             }
12445
12446             has_book_hit += in_book;
12447
12448             if( ! in_book ) {
12449                 result = index;
12450
12451                 break;
12452             }
12453
12454             index += 2;
12455         }
12456     }
12457
12458     return result;
12459 }
12460
12461 void
12462 GetOutOfBookInfo (char * buf)
12463 {
12464     int oob[2];
12465     int i;
12466     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12467
12468     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12469     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12470
12471     *buf = '\0';
12472
12473     if( oob[0] >= 0 || oob[1] >= 0 ) {
12474         for( i=0; i<2; i++ ) {
12475             int idx = oob[i];
12476
12477             if( idx >= 0 ) {
12478                 if( i > 0 && oob[0] >= 0 ) {
12479                     strcat( buf, "   " );
12480                 }
12481
12482                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12483                 sprintf( buf+strlen(buf), "%s%.2f",
12484                     pvInfoList[idx].score >= 0 ? "+" : "",
12485                     pvInfoList[idx].score / 100.0 );
12486             }
12487         }
12488     }
12489 }
12490
12491 /* Save game in PGN style and close the file */
12492 int
12493 SaveGamePGN (FILE *f)
12494 {
12495     int i, offset, linelen, newblock;
12496     time_t tm;
12497 //    char *movetext;
12498     char numtext[32];
12499     int movelen, numlen, blank;
12500     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12501
12502     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12503
12504     tm = time((time_t *) NULL);
12505
12506     PrintPGNTags(f, &gameInfo);
12507
12508     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12509
12510     if (backwardMostMove > 0 || startedFromSetupPosition) {
12511         char *fen = PositionToFEN(backwardMostMove, NULL);
12512         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12513         fprintf(f, "\n{--------------\n");
12514         PrintPosition(f, backwardMostMove);
12515         fprintf(f, "--------------}\n");
12516         free(fen);
12517     }
12518     else {
12519         /* [AS] Out of book annotation */
12520         if( appData.saveOutOfBookInfo ) {
12521             char buf[64];
12522
12523             GetOutOfBookInfo( buf );
12524
12525             if( buf[0] != '\0' ) {
12526                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12527             }
12528         }
12529
12530         fprintf(f, "\n");
12531     }
12532
12533     i = backwardMostMove;
12534     linelen = 0;
12535     newblock = TRUE;
12536
12537     while (i < forwardMostMove) {
12538         /* Print comments preceding this move */
12539         if (commentList[i] != NULL) {
12540             if (linelen > 0) fprintf(f, "\n");
12541             fprintf(f, "%s", commentList[i]);
12542             linelen = 0;
12543             newblock = TRUE;
12544         }
12545
12546         /* Format move number */
12547         if ((i % 2) == 0)
12548           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12549         else
12550           if (newblock)
12551             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12552           else
12553             numtext[0] = NULLCHAR;
12554
12555         numlen = strlen(numtext);
12556         newblock = FALSE;
12557
12558         /* Print move number */
12559         blank = linelen > 0 && numlen > 0;
12560         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12561             fprintf(f, "\n");
12562             linelen = 0;
12563             blank = 0;
12564         }
12565         if (blank) {
12566             fprintf(f, " ");
12567             linelen++;
12568         }
12569         fprintf(f, "%s", numtext);
12570         linelen += numlen;
12571
12572         /* Get move */
12573         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12574         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12575
12576         /* Print move */
12577         blank = linelen > 0 && movelen > 0;
12578         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12579             fprintf(f, "\n");
12580             linelen = 0;
12581             blank = 0;
12582         }
12583         if (blank) {
12584             fprintf(f, " ");
12585             linelen++;
12586         }
12587         fprintf(f, "%s", move_buffer);
12588         linelen += movelen;
12589
12590         /* [AS] Add PV info if present */
12591         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12592             /* [HGM] add time */
12593             char buf[MSG_SIZ]; int seconds;
12594
12595             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12596
12597             if( seconds <= 0)
12598               buf[0] = 0;
12599             else
12600               if( seconds < 30 )
12601                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12602               else
12603                 {
12604                   seconds = (seconds + 4)/10; // round to full seconds
12605                   if( seconds < 60 )
12606                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12607                   else
12608                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12609                 }
12610
12611             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12612                       pvInfoList[i].score >= 0 ? "+" : "",
12613                       pvInfoList[i].score / 100.0,
12614                       pvInfoList[i].depth,
12615                       buf );
12616
12617             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12618
12619             /* Print score/depth */
12620             blank = linelen > 0 && movelen > 0;
12621             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12622                 fprintf(f, "\n");
12623                 linelen = 0;
12624                 blank = 0;
12625             }
12626             if (blank) {
12627                 fprintf(f, " ");
12628                 linelen++;
12629             }
12630             fprintf(f, "%s", move_buffer);
12631             linelen += movelen;
12632         }
12633
12634         i++;
12635     }
12636
12637     /* Start a new line */
12638     if (linelen > 0) fprintf(f, "\n");
12639
12640     /* Print comments after last move */
12641     if (commentList[i] != NULL) {
12642         fprintf(f, "%s\n", commentList[i]);
12643     }
12644
12645     /* Print result */
12646     if (gameInfo.resultDetails != NULL &&
12647         gameInfo.resultDetails[0] != NULLCHAR) {
12648         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12649                 PGNResult(gameInfo.result));
12650     } else {
12651         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12652     }
12653
12654     fclose(f);
12655     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12656     return TRUE;
12657 }
12658
12659 /* Save game in old style and close the file */
12660 int
12661 SaveGameOldStyle (FILE *f)
12662 {
12663     int i, offset;
12664     time_t tm;
12665
12666     tm = time((time_t *) NULL);
12667
12668     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12669     PrintOpponents(f);
12670
12671     if (backwardMostMove > 0 || startedFromSetupPosition) {
12672         fprintf(f, "\n[--------------\n");
12673         PrintPosition(f, backwardMostMove);
12674         fprintf(f, "--------------]\n");
12675     } else {
12676         fprintf(f, "\n");
12677     }
12678
12679     i = backwardMostMove;
12680     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12681
12682     while (i < forwardMostMove) {
12683         if (commentList[i] != NULL) {
12684             fprintf(f, "[%s]\n", commentList[i]);
12685         }
12686
12687         if ((i % 2) == 1) {
12688             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12689             i++;
12690         } else {
12691             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12692             i++;
12693             if (commentList[i] != NULL) {
12694                 fprintf(f, "\n");
12695                 continue;
12696             }
12697             if (i >= forwardMostMove) {
12698                 fprintf(f, "\n");
12699                 break;
12700             }
12701             fprintf(f, "%s\n", parseList[i]);
12702             i++;
12703         }
12704     }
12705
12706     if (commentList[i] != NULL) {
12707         fprintf(f, "[%s]\n", commentList[i]);
12708     }
12709
12710     /* This isn't really the old style, but it's close enough */
12711     if (gameInfo.resultDetails != NULL &&
12712         gameInfo.resultDetails[0] != NULLCHAR) {
12713         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12714                 gameInfo.resultDetails);
12715     } else {
12716         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12717     }
12718
12719     fclose(f);
12720     return TRUE;
12721 }
12722
12723 /* Save the current game to open file f and close the file */
12724 int
12725 SaveGame (FILE *f, int dummy, char *dummy2)
12726 {
12727     if (gameMode == EditPosition) EditPositionDone(TRUE);
12728     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12729     if (appData.oldSaveStyle)
12730       return SaveGameOldStyle(f);
12731     else
12732       return SaveGamePGN(f);
12733 }
12734
12735 /* Save the current position to the given file */
12736 int
12737 SavePositionToFile (char *filename)
12738 {
12739     FILE *f;
12740     char buf[MSG_SIZ];
12741
12742     if (strcmp(filename, "-") == 0) {
12743         return SavePosition(stdout, 0, NULL);
12744     } else {
12745         f = fopen(filename, "a");
12746         if (f == NULL) {
12747             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12748             DisplayError(buf, errno);
12749             return FALSE;
12750         } else {
12751             safeStrCpy(buf, lastMsg, MSG_SIZ);
12752             DisplayMessage(_("Waiting for access to save file"), "");
12753             flock(fileno(f), LOCK_EX); // [HGM] lock
12754             DisplayMessage(_("Saving position"), "");
12755             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12756             SavePosition(f, 0, NULL);
12757             DisplayMessage(buf, "");
12758             return TRUE;
12759         }
12760     }
12761 }
12762
12763 /* Save the current position to the given open file and close the file */
12764 int
12765 SavePosition (FILE *f, int dummy, char *dummy2)
12766 {
12767     time_t tm;
12768     char *fen;
12769
12770     if (gameMode == EditPosition) EditPositionDone(TRUE);
12771     if (appData.oldSaveStyle) {
12772         tm = time((time_t *) NULL);
12773
12774         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12775         PrintOpponents(f);
12776         fprintf(f, "[--------------\n");
12777         PrintPosition(f, currentMove);
12778         fprintf(f, "--------------]\n");
12779     } else {
12780         fen = PositionToFEN(currentMove, NULL);
12781         fprintf(f, "%s\n", fen);
12782         free(fen);
12783     }
12784     fclose(f);
12785     return TRUE;
12786 }
12787
12788 void
12789 ReloadCmailMsgEvent (int unregister)
12790 {
12791 #if !WIN32
12792     static char *inFilename = NULL;
12793     static char *outFilename;
12794     int i;
12795     struct stat inbuf, outbuf;
12796     int status;
12797
12798     /* Any registered moves are unregistered if unregister is set, */
12799     /* i.e. invoked by the signal handler */
12800     if (unregister) {
12801         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12802             cmailMoveRegistered[i] = FALSE;
12803             if (cmailCommentList[i] != NULL) {
12804                 free(cmailCommentList[i]);
12805                 cmailCommentList[i] = NULL;
12806             }
12807         }
12808         nCmailMovesRegistered = 0;
12809     }
12810
12811     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12812         cmailResult[i] = CMAIL_NOT_RESULT;
12813     }
12814     nCmailResults = 0;
12815
12816     if (inFilename == NULL) {
12817         /* Because the filenames are static they only get malloced once  */
12818         /* and they never get freed                                      */
12819         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12820         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12821
12822         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12823         sprintf(outFilename, "%s.out", appData.cmailGameName);
12824     }
12825
12826     status = stat(outFilename, &outbuf);
12827     if (status < 0) {
12828         cmailMailedMove = FALSE;
12829     } else {
12830         status = stat(inFilename, &inbuf);
12831         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12832     }
12833
12834     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12835        counts the games, notes how each one terminated, etc.
12836
12837        It would be nice to remove this kludge and instead gather all
12838        the information while building the game list.  (And to keep it
12839        in the game list nodes instead of having a bunch of fixed-size
12840        parallel arrays.)  Note this will require getting each game's
12841        termination from the PGN tags, as the game list builder does
12842        not process the game moves.  --mann
12843        */
12844     cmailMsgLoaded = TRUE;
12845     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12846
12847     /* Load first game in the file or popup game menu */
12848     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12849
12850 #endif /* !WIN32 */
12851     return;
12852 }
12853
12854 int
12855 RegisterMove ()
12856 {
12857     FILE *f;
12858     char string[MSG_SIZ];
12859
12860     if (   cmailMailedMove
12861         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12862         return TRUE;            /* Allow free viewing  */
12863     }
12864
12865     /* Unregister move to ensure that we don't leave RegisterMove        */
12866     /* with the move registered when the conditions for registering no   */
12867     /* longer hold                                                       */
12868     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12869         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12870         nCmailMovesRegistered --;
12871
12872         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12873           {
12874               free(cmailCommentList[lastLoadGameNumber - 1]);
12875               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12876           }
12877     }
12878
12879     if (cmailOldMove == -1) {
12880         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12881         return FALSE;
12882     }
12883
12884     if (currentMove > cmailOldMove + 1) {
12885         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12886         return FALSE;
12887     }
12888
12889     if (currentMove < cmailOldMove) {
12890         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12891         return FALSE;
12892     }
12893
12894     if (forwardMostMove > currentMove) {
12895         /* Silently truncate extra moves */
12896         TruncateGame();
12897     }
12898
12899     if (   (currentMove == cmailOldMove + 1)
12900         || (   (currentMove == cmailOldMove)
12901             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12902                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12903         if (gameInfo.result != GameUnfinished) {
12904             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12905         }
12906
12907         if (commentList[currentMove] != NULL) {
12908             cmailCommentList[lastLoadGameNumber - 1]
12909               = StrSave(commentList[currentMove]);
12910         }
12911         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12912
12913         if (appData.debugMode)
12914           fprintf(debugFP, "Saving %s for game %d\n",
12915                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12916
12917         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12918
12919         f = fopen(string, "w");
12920         if (appData.oldSaveStyle) {
12921             SaveGameOldStyle(f); /* also closes the file */
12922
12923             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12924             f = fopen(string, "w");
12925             SavePosition(f, 0, NULL); /* also closes the file */
12926         } else {
12927             fprintf(f, "{--------------\n");
12928             PrintPosition(f, currentMove);
12929             fprintf(f, "--------------}\n\n");
12930
12931             SaveGame(f, 0, NULL); /* also closes the file*/
12932         }
12933
12934         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12935         nCmailMovesRegistered ++;
12936     } else if (nCmailGames == 1) {
12937         DisplayError(_("You have not made a move yet"), 0);
12938         return FALSE;
12939     }
12940
12941     return TRUE;
12942 }
12943
12944 void
12945 MailMoveEvent ()
12946 {
12947 #if !WIN32
12948     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12949     FILE *commandOutput;
12950     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12951     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12952     int nBuffers;
12953     int i;
12954     int archived;
12955     char *arcDir;
12956
12957     if (! cmailMsgLoaded) {
12958         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12959         return;
12960     }
12961
12962     if (nCmailGames == nCmailResults) {
12963         DisplayError(_("No unfinished games"), 0);
12964         return;
12965     }
12966
12967 #if CMAIL_PROHIBIT_REMAIL
12968     if (cmailMailedMove) {
12969       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);
12970         DisplayError(msg, 0);
12971         return;
12972     }
12973 #endif
12974
12975     if (! (cmailMailedMove || RegisterMove())) return;
12976
12977     if (   cmailMailedMove
12978         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12979       snprintf(string, MSG_SIZ, partCommandString,
12980                appData.debugMode ? " -v" : "", appData.cmailGameName);
12981         commandOutput = popen(string, "r");
12982
12983         if (commandOutput == NULL) {
12984             DisplayError(_("Failed to invoke cmail"), 0);
12985         } else {
12986             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12987                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12988             }
12989             if (nBuffers > 1) {
12990                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12991                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12992                 nBytes = MSG_SIZ - 1;
12993             } else {
12994                 (void) memcpy(msg, buffer, nBytes);
12995             }
12996             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12997
12998             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12999                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13000
13001                 archived = TRUE;
13002                 for (i = 0; i < nCmailGames; i ++) {
13003                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13004                         archived = FALSE;
13005                     }
13006                 }
13007                 if (   archived
13008                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13009                         != NULL)) {
13010                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13011                            arcDir,
13012                            appData.cmailGameName,
13013                            gameInfo.date);
13014                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13015                     cmailMsgLoaded = FALSE;
13016                 }
13017             }
13018
13019             DisplayInformation(msg);
13020             pclose(commandOutput);
13021         }
13022     } else {
13023         if ((*cmailMsg) != '\0') {
13024             DisplayInformation(cmailMsg);
13025         }
13026     }
13027
13028     return;
13029 #endif /* !WIN32 */
13030 }
13031
13032 char *
13033 CmailMsg ()
13034 {
13035 #if WIN32
13036     return NULL;
13037 #else
13038     int  prependComma = 0;
13039     char number[5];
13040     char string[MSG_SIZ];       /* Space for game-list */
13041     int  i;
13042
13043     if (!cmailMsgLoaded) return "";
13044
13045     if (cmailMailedMove) {
13046       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13047     } else {
13048         /* Create a list of games left */
13049       snprintf(string, MSG_SIZ, "[");
13050         for (i = 0; i < nCmailGames; i ++) {
13051             if (! (   cmailMoveRegistered[i]
13052                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13053                 if (prependComma) {
13054                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13055                 } else {
13056                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13057                     prependComma = 1;
13058                 }
13059
13060                 strcat(string, number);
13061             }
13062         }
13063         strcat(string, "]");
13064
13065         if (nCmailMovesRegistered + nCmailResults == 0) {
13066             switch (nCmailGames) {
13067               case 1:
13068                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13069                 break;
13070
13071               case 2:
13072                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13073                 break;
13074
13075               default:
13076                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13077                          nCmailGames);
13078                 break;
13079             }
13080         } else {
13081             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13082               case 1:
13083                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13084                          string);
13085                 break;
13086
13087               case 0:
13088                 if (nCmailResults == nCmailGames) {
13089                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13090                 } else {
13091                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13092                 }
13093                 break;
13094
13095               default:
13096                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13097                          string);
13098             }
13099         }
13100     }
13101     return cmailMsg;
13102 #endif /* WIN32 */
13103 }
13104
13105 void
13106 ResetGameEvent ()
13107 {
13108     if (gameMode == Training)
13109       SetTrainingModeOff();
13110
13111     Reset(TRUE, TRUE);
13112     cmailMsgLoaded = FALSE;
13113     if (appData.icsActive) {
13114       SendToICS(ics_prefix);
13115       SendToICS("refresh\n");
13116     }
13117 }
13118
13119 void
13120 ExitEvent (int status)
13121 {
13122     exiting++;
13123     if (exiting > 2) {
13124       /* Give up on clean exit */
13125       exit(status);
13126     }
13127     if (exiting > 1) {
13128       /* Keep trying for clean exit */
13129       return;
13130     }
13131
13132     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13133
13134     if (telnetISR != NULL) {
13135       RemoveInputSource(telnetISR);
13136     }
13137     if (icsPR != NoProc) {
13138       DestroyChildProcess(icsPR, TRUE);
13139     }
13140
13141     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13142     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13143
13144     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13145     /* make sure this other one finishes before killing it!                  */
13146     if(endingGame) { int count = 0;
13147         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13148         while(endingGame && count++ < 10) DoSleep(1);
13149         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13150     }
13151
13152     /* Kill off chess programs */
13153     if (first.pr != NoProc) {
13154         ExitAnalyzeMode();
13155
13156         DoSleep( appData.delayBeforeQuit );
13157         SendToProgram("quit\n", &first);
13158         DoSleep( appData.delayAfterQuit );
13159         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13160     }
13161     if (second.pr != NoProc) {
13162         DoSleep( appData.delayBeforeQuit );
13163         SendToProgram("quit\n", &second);
13164         DoSleep( appData.delayAfterQuit );
13165         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13166     }
13167     if (first.isr != NULL) {
13168         RemoveInputSource(first.isr);
13169     }
13170     if (second.isr != NULL) {
13171         RemoveInputSource(second.isr);
13172     }
13173
13174     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13175     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13176
13177     ShutDownFrontEnd();
13178     exit(status);
13179 }
13180
13181 void
13182 PauseEvent ()
13183 {
13184     if (appData.debugMode)
13185         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13186     if (pausing) {
13187         pausing = FALSE;
13188         ModeHighlight();
13189         if (gameMode == MachinePlaysWhite ||
13190             gameMode == MachinePlaysBlack) {
13191             StartClocks();
13192         } else {
13193             DisplayBothClocks();
13194         }
13195         if (gameMode == PlayFromGameFile) {
13196             if (appData.timeDelay >= 0)
13197                 AutoPlayGameLoop();
13198         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13199             Reset(FALSE, TRUE);
13200             SendToICS(ics_prefix);
13201             SendToICS("refresh\n");
13202         } else if (currentMove < forwardMostMove) {
13203             ForwardInner(forwardMostMove);
13204         }
13205         pauseExamInvalid = FALSE;
13206     } else {
13207         switch (gameMode) {
13208           default:
13209             return;
13210           case IcsExamining:
13211             pauseExamForwardMostMove = forwardMostMove;
13212             pauseExamInvalid = FALSE;
13213             /* fall through */
13214           case IcsObserving:
13215           case IcsPlayingWhite:
13216           case IcsPlayingBlack:
13217             pausing = TRUE;
13218             ModeHighlight();
13219             return;
13220           case PlayFromGameFile:
13221             (void) StopLoadGameTimer();
13222             pausing = TRUE;
13223             ModeHighlight();
13224             break;
13225           case BeginningOfGame:
13226             if (appData.icsActive) return;
13227             /* else fall through */
13228           case MachinePlaysWhite:
13229           case MachinePlaysBlack:
13230           case TwoMachinesPlay:
13231             if (forwardMostMove == 0)
13232               return;           /* don't pause if no one has moved */
13233             if ((gameMode == MachinePlaysWhite &&
13234                  !WhiteOnMove(forwardMostMove)) ||
13235                 (gameMode == MachinePlaysBlack &&
13236                  WhiteOnMove(forwardMostMove))) {
13237                 StopClocks();
13238             }
13239             pausing = TRUE;
13240             ModeHighlight();
13241             break;
13242         }
13243     }
13244 }
13245
13246 void
13247 EditCommentEvent ()
13248 {
13249     char title[MSG_SIZ];
13250
13251     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13252       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13253     } else {
13254       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13255                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13256                parseList[currentMove - 1]);
13257     }
13258
13259     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13260 }
13261
13262
13263 void
13264 EditTagsEvent ()
13265 {
13266     char *tags = PGNTags(&gameInfo);
13267     bookUp = FALSE;
13268     EditTagsPopUp(tags, NULL);
13269     free(tags);
13270 }
13271
13272 void
13273 AnalyzeModeEvent ()
13274 {
13275     if (appData.noChessProgram || gameMode == AnalyzeMode)
13276       return;
13277
13278     if (gameMode != AnalyzeFile) {
13279         if (!appData.icsEngineAnalyze) {
13280                EditGameEvent();
13281                if (gameMode != EditGame) return;
13282         }
13283         ResurrectChessProgram();
13284         SendToProgram("analyze\n", &first);
13285         first.analyzing = TRUE;
13286         /*first.maybeThinking = TRUE;*/
13287         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13288         EngineOutputPopUp();
13289     }
13290     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13291     pausing = FALSE;
13292     ModeHighlight();
13293     SetGameInfo();
13294
13295     StartAnalysisClock();
13296     GetTimeMark(&lastNodeCountTime);
13297     lastNodeCount = 0;
13298 }
13299
13300 void
13301 AnalyzeFileEvent ()
13302 {
13303     if (appData.noChessProgram || gameMode == AnalyzeFile)
13304       return;
13305
13306     if (gameMode != AnalyzeMode) {
13307         EditGameEvent();
13308         if (gameMode != EditGame) return;
13309         ResurrectChessProgram();
13310         SendToProgram("analyze\n", &first);
13311         first.analyzing = TRUE;
13312         /*first.maybeThinking = TRUE;*/
13313         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13314         EngineOutputPopUp();
13315     }
13316     gameMode = AnalyzeFile;
13317     pausing = FALSE;
13318     ModeHighlight();
13319     SetGameInfo();
13320
13321     StartAnalysisClock();
13322     GetTimeMark(&lastNodeCountTime);
13323     lastNodeCount = 0;
13324     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13325 }
13326
13327 void
13328 MachineWhiteEvent ()
13329 {
13330     char buf[MSG_SIZ];
13331     char *bookHit = NULL;
13332
13333     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13334       return;
13335
13336
13337     if (gameMode == PlayFromGameFile ||
13338         gameMode == TwoMachinesPlay  ||
13339         gameMode == Training         ||
13340         gameMode == AnalyzeMode      ||
13341         gameMode == EndOfGame)
13342         EditGameEvent();
13343
13344     if (gameMode == EditPosition)
13345         EditPositionDone(TRUE);
13346
13347     if (!WhiteOnMove(currentMove)) {
13348         DisplayError(_("It is not White's turn"), 0);
13349         return;
13350     }
13351
13352     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13353       ExitAnalyzeMode();
13354
13355     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13356         gameMode == AnalyzeFile)
13357         TruncateGame();
13358
13359     ResurrectChessProgram();    /* in case it isn't running */
13360     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13361         gameMode = MachinePlaysWhite;
13362         ResetClocks();
13363     } else
13364     gameMode = MachinePlaysWhite;
13365     pausing = FALSE;
13366     ModeHighlight();
13367     SetGameInfo();
13368     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13369     DisplayTitle(buf);
13370     if (first.sendName) {
13371       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13372       SendToProgram(buf, &first);
13373     }
13374     if (first.sendTime) {
13375       if (first.useColors) {
13376         SendToProgram("black\n", &first); /*gnu kludge*/
13377       }
13378       SendTimeRemaining(&first, TRUE);
13379     }
13380     if (first.useColors) {
13381       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13382     }
13383     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13384     SetMachineThinkingEnables();
13385     first.maybeThinking = TRUE;
13386     StartClocks();
13387     firstMove = FALSE;
13388
13389     if (appData.autoFlipView && !flipView) {
13390       flipView = !flipView;
13391       DrawPosition(FALSE, NULL);
13392       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13393     }
13394
13395     if(bookHit) { // [HGM] book: simulate book reply
13396         static char bookMove[MSG_SIZ]; // a bit generous?
13397
13398         programStats.nodes = programStats.depth = programStats.time =
13399         programStats.score = programStats.got_only_move = 0;
13400         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13401
13402         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13403         strcat(bookMove, bookHit);
13404         HandleMachineMove(bookMove, &first);
13405     }
13406 }
13407
13408 void
13409 MachineBlackEvent ()
13410 {
13411   char buf[MSG_SIZ];
13412   char *bookHit = NULL;
13413
13414     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13415         return;
13416
13417
13418     if (gameMode == PlayFromGameFile ||
13419         gameMode == TwoMachinesPlay  ||
13420         gameMode == Training         ||
13421         gameMode == AnalyzeMode      ||
13422         gameMode == EndOfGame)
13423         EditGameEvent();
13424
13425     if (gameMode == EditPosition)
13426         EditPositionDone(TRUE);
13427
13428     if (WhiteOnMove(currentMove)) {
13429         DisplayError(_("It is not Black's turn"), 0);
13430         return;
13431     }
13432
13433     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13434       ExitAnalyzeMode();
13435
13436     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13437         gameMode == AnalyzeFile)
13438         TruncateGame();
13439
13440     ResurrectChessProgram();    /* in case it isn't running */
13441     gameMode = MachinePlaysBlack;
13442     pausing = FALSE;
13443     ModeHighlight();
13444     SetGameInfo();
13445     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13446     DisplayTitle(buf);
13447     if (first.sendName) {
13448       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13449       SendToProgram(buf, &first);
13450     }
13451     if (first.sendTime) {
13452       if (first.useColors) {
13453         SendToProgram("white\n", &first); /*gnu kludge*/
13454       }
13455       SendTimeRemaining(&first, FALSE);
13456     }
13457     if (first.useColors) {
13458       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13459     }
13460     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13461     SetMachineThinkingEnables();
13462     first.maybeThinking = TRUE;
13463     StartClocks();
13464
13465     if (appData.autoFlipView && flipView) {
13466       flipView = !flipView;
13467       DrawPosition(FALSE, NULL);
13468       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13469     }
13470     if(bookHit) { // [HGM] book: simulate book reply
13471         static char bookMove[MSG_SIZ]; // a bit generous?
13472
13473         programStats.nodes = programStats.depth = programStats.time =
13474         programStats.score = programStats.got_only_move = 0;
13475         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13476
13477         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13478         strcat(bookMove, bookHit);
13479         HandleMachineMove(bookMove, &first);
13480     }
13481 }
13482
13483
13484 void
13485 DisplayTwoMachinesTitle ()
13486 {
13487     char buf[MSG_SIZ];
13488     if (appData.matchGames > 0) {
13489         if(appData.tourneyFile[0]) {
13490           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13491                    gameInfo.white, _("vs."), gameInfo.black,
13492                    nextGame+1, appData.matchGames+1,
13493                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13494         } else 
13495         if (first.twoMachinesColor[0] == 'w') {
13496           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13497                    gameInfo.white, _("vs."),  gameInfo.black,
13498                    first.matchWins, second.matchWins,
13499                    matchGame - 1 - (first.matchWins + second.matchWins));
13500         } else {
13501           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13502                    gameInfo.white, _("vs."), gameInfo.black,
13503                    second.matchWins, first.matchWins,
13504                    matchGame - 1 - (first.matchWins + second.matchWins));
13505         }
13506     } else {
13507       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13508     }
13509     DisplayTitle(buf);
13510 }
13511
13512 void
13513 SettingsMenuIfReady ()
13514 {
13515   if (second.lastPing != second.lastPong) {
13516     DisplayMessage("", _("Waiting for second chess program"));
13517     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13518     return;
13519   }
13520   ThawUI();
13521   DisplayMessage("", "");
13522   SettingsPopUp(&second);
13523 }
13524
13525 int
13526 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13527 {
13528     char buf[MSG_SIZ];
13529     if (cps->pr == NoProc) {
13530         StartChessProgram(cps);
13531         if (cps->protocolVersion == 1) {
13532           retry();
13533         } else {
13534           /* kludge: allow timeout for initial "feature" command */
13535           FreezeUI();
13536           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13537           DisplayMessage("", buf);
13538           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13539         }
13540         return 1;
13541     }
13542     return 0;
13543 }
13544
13545 void
13546 TwoMachinesEvent P((void))
13547 {
13548     int i;
13549     char buf[MSG_SIZ];
13550     ChessProgramState *onmove;
13551     char *bookHit = NULL;
13552     static int stalling = 0;
13553     TimeMark now;
13554     long wait;
13555
13556     if (appData.noChessProgram) return;
13557
13558     switch (gameMode) {
13559       case TwoMachinesPlay:
13560         return;
13561       case MachinePlaysWhite:
13562       case MachinePlaysBlack:
13563         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13564             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13565             return;
13566         }
13567         /* fall through */
13568       case BeginningOfGame:
13569       case PlayFromGameFile:
13570       case EndOfGame:
13571         EditGameEvent();
13572         if (gameMode != EditGame) return;
13573         break;
13574       case EditPosition:
13575         EditPositionDone(TRUE);
13576         break;
13577       case AnalyzeMode:
13578       case AnalyzeFile:
13579         ExitAnalyzeMode();
13580         break;
13581       case EditGame:
13582       default:
13583         break;
13584     }
13585
13586 //    forwardMostMove = currentMove;
13587     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13588
13589     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13590
13591     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13592     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13593       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13594       return;
13595     }
13596     if(!stalling) {
13597       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13598       SendToProgram("force\n", &second);
13599       stalling = 1;
13600       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13601       return;
13602     }
13603     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13604     if(appData.matchPause>10000 || appData.matchPause<10)
13605                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13606     wait = SubtractTimeMarks(&now, &pauseStart);
13607     if(wait < appData.matchPause) {
13608         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13609         return;
13610     }
13611     // we are now committed to starting the game
13612     stalling = 0;
13613     DisplayMessage("", "");
13614     if (startedFromSetupPosition) {
13615         SendBoard(&second, backwardMostMove);
13616     if (appData.debugMode) {
13617         fprintf(debugFP, "Two Machines\n");
13618     }
13619     }
13620     for (i = backwardMostMove; i < forwardMostMove; i++) {
13621         SendMoveToProgram(i, &second);
13622     }
13623
13624     gameMode = TwoMachinesPlay;
13625     pausing = FALSE;
13626     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13627     SetGameInfo();
13628     DisplayTwoMachinesTitle();
13629     firstMove = TRUE;
13630     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13631         onmove = &first;
13632     } else {
13633         onmove = &second;
13634     }
13635     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13636     SendToProgram(first.computerString, &first);
13637     if (first.sendName) {
13638       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13639       SendToProgram(buf, &first);
13640     }
13641     SendToProgram(second.computerString, &second);
13642     if (second.sendName) {
13643       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13644       SendToProgram(buf, &second);
13645     }
13646
13647     ResetClocks();
13648     if (!first.sendTime || !second.sendTime) {
13649         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13650         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13651     }
13652     if (onmove->sendTime) {
13653       if (onmove->useColors) {
13654         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13655       }
13656       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13657     }
13658     if (onmove->useColors) {
13659       SendToProgram(onmove->twoMachinesColor, onmove);
13660     }
13661     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13662 //    SendToProgram("go\n", onmove);
13663     onmove->maybeThinking = TRUE;
13664     SetMachineThinkingEnables();
13665
13666     StartClocks();
13667
13668     if(bookHit) { // [HGM] book: simulate book reply
13669         static char bookMove[MSG_SIZ]; // a bit generous?
13670
13671         programStats.nodes = programStats.depth = programStats.time =
13672         programStats.score = programStats.got_only_move = 0;
13673         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13674
13675         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13676         strcat(bookMove, bookHit);
13677         savedMessage = bookMove; // args for deferred call
13678         savedState = onmove;
13679         ScheduleDelayedEvent(DeferredBookMove, 1);
13680     }
13681 }
13682
13683 void
13684 TrainingEvent ()
13685 {
13686     if (gameMode == Training) {
13687       SetTrainingModeOff();
13688       gameMode = PlayFromGameFile;
13689       DisplayMessage("", _("Training mode off"));
13690     } else {
13691       gameMode = Training;
13692       animateTraining = appData.animate;
13693
13694       /* make sure we are not already at the end of the game */
13695       if (currentMove < forwardMostMove) {
13696         SetTrainingModeOn();
13697         DisplayMessage("", _("Training mode on"));
13698       } else {
13699         gameMode = PlayFromGameFile;
13700         DisplayError(_("Already at end of game"), 0);
13701       }
13702     }
13703     ModeHighlight();
13704 }
13705
13706 void
13707 IcsClientEvent ()
13708 {
13709     if (!appData.icsActive) return;
13710     switch (gameMode) {
13711       case IcsPlayingWhite:
13712       case IcsPlayingBlack:
13713       case IcsObserving:
13714       case IcsIdle:
13715       case BeginningOfGame:
13716       case IcsExamining:
13717         return;
13718
13719       case EditGame:
13720         break;
13721
13722       case EditPosition:
13723         EditPositionDone(TRUE);
13724         break;
13725
13726       case AnalyzeMode:
13727       case AnalyzeFile:
13728         ExitAnalyzeMode();
13729         break;
13730
13731       default:
13732         EditGameEvent();
13733         break;
13734     }
13735
13736     gameMode = IcsIdle;
13737     ModeHighlight();
13738     return;
13739 }
13740
13741 void
13742 EditGameEvent ()
13743 {
13744     int i;
13745
13746     switch (gameMode) {
13747       case Training:
13748         SetTrainingModeOff();
13749         break;
13750       case MachinePlaysWhite:
13751       case MachinePlaysBlack:
13752       case BeginningOfGame:
13753         SendToProgram("force\n", &first);
13754         SetUserThinkingEnables();
13755         break;
13756       case PlayFromGameFile:
13757         (void) StopLoadGameTimer();
13758         if (gameFileFP != NULL) {
13759             gameFileFP = NULL;
13760         }
13761         break;
13762       case EditPosition:
13763         EditPositionDone(TRUE);
13764         break;
13765       case AnalyzeMode:
13766       case AnalyzeFile:
13767         ExitAnalyzeMode();
13768         SendToProgram("force\n", &first);
13769         break;
13770       case TwoMachinesPlay:
13771         GameEnds(EndOfFile, NULL, GE_PLAYER);
13772         ResurrectChessProgram();
13773         SetUserThinkingEnables();
13774         break;
13775       case EndOfGame:
13776         ResurrectChessProgram();
13777         break;
13778       case IcsPlayingBlack:
13779       case IcsPlayingWhite:
13780         DisplayError(_("Warning: You are still playing a game"), 0);
13781         break;
13782       case IcsObserving:
13783         DisplayError(_("Warning: You are still observing a game"), 0);
13784         break;
13785       case IcsExamining:
13786         DisplayError(_("Warning: You are still examining a game"), 0);
13787         break;
13788       case IcsIdle:
13789         break;
13790       case EditGame:
13791       default:
13792         return;
13793     }
13794
13795     pausing = FALSE;
13796     StopClocks();
13797     first.offeredDraw = second.offeredDraw = 0;
13798
13799     if (gameMode == PlayFromGameFile) {
13800         whiteTimeRemaining = timeRemaining[0][currentMove];
13801         blackTimeRemaining = timeRemaining[1][currentMove];
13802         DisplayTitle("");
13803     }
13804
13805     if (gameMode == MachinePlaysWhite ||
13806         gameMode == MachinePlaysBlack ||
13807         gameMode == TwoMachinesPlay ||
13808         gameMode == EndOfGame) {
13809         i = forwardMostMove;
13810         while (i > currentMove) {
13811             SendToProgram("undo\n", &first);
13812             i--;
13813         }
13814         if(!adjustedClock) {
13815         whiteTimeRemaining = timeRemaining[0][currentMove];
13816         blackTimeRemaining = timeRemaining[1][currentMove];
13817         DisplayBothClocks();
13818         }
13819         if (whiteFlag || blackFlag) {
13820             whiteFlag = blackFlag = 0;
13821         }
13822         DisplayTitle("");
13823     }
13824
13825     gameMode = EditGame;
13826     ModeHighlight();
13827     SetGameInfo();
13828 }
13829
13830
13831 void
13832 EditPositionEvent ()
13833 {
13834     if (gameMode == EditPosition) {
13835         EditGameEvent();
13836         return;
13837     }
13838
13839     EditGameEvent();
13840     if (gameMode != EditGame) return;
13841
13842     gameMode = EditPosition;
13843     ModeHighlight();
13844     SetGameInfo();
13845     if (currentMove > 0)
13846       CopyBoard(boards[0], boards[currentMove]);
13847
13848     blackPlaysFirst = !WhiteOnMove(currentMove);
13849     ResetClocks();
13850     currentMove = forwardMostMove = backwardMostMove = 0;
13851     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13852     DisplayMove(-1);
13853     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13854 }
13855
13856 void
13857 ExitAnalyzeMode ()
13858 {
13859     /* [DM] icsEngineAnalyze - possible call from other functions */
13860     if (appData.icsEngineAnalyze) {
13861         appData.icsEngineAnalyze = FALSE;
13862
13863         DisplayMessage("",_("Close ICS engine analyze..."));
13864     }
13865     if (first.analysisSupport && first.analyzing) {
13866       SendToProgram("exit\n", &first);
13867       first.analyzing = FALSE;
13868     }
13869     thinkOutput[0] = NULLCHAR;
13870 }
13871
13872 void
13873 EditPositionDone (Boolean fakeRights)
13874 {
13875     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13876
13877     startedFromSetupPosition = TRUE;
13878     InitChessProgram(&first, FALSE);
13879     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13880       boards[0][EP_STATUS] = EP_NONE;
13881       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13882     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13883         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13884         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13885       } else boards[0][CASTLING][2] = NoRights;
13886     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13887         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13888         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13889       } else boards[0][CASTLING][5] = NoRights;
13890     }
13891     SendToProgram("force\n", &first);
13892     if (blackPlaysFirst) {
13893         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13894         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13895         currentMove = forwardMostMove = backwardMostMove = 1;
13896         CopyBoard(boards[1], boards[0]);
13897     } else {
13898         currentMove = forwardMostMove = backwardMostMove = 0;
13899     }
13900     SendBoard(&first, forwardMostMove);
13901     if (appData.debugMode) {
13902         fprintf(debugFP, "EditPosDone\n");
13903     }
13904     DisplayTitle("");
13905     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13906     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13907     gameMode = EditGame;
13908     ModeHighlight();
13909     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13910     ClearHighlights(); /* [AS] */
13911 }
13912
13913 /* Pause for `ms' milliseconds */
13914 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13915 void
13916 TimeDelay (long ms)
13917 {
13918     TimeMark m1, m2;
13919
13920     GetTimeMark(&m1);
13921     do {
13922         GetTimeMark(&m2);
13923     } while (SubtractTimeMarks(&m2, &m1) < ms);
13924 }
13925
13926 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13927 void
13928 SendMultiLineToICS (char *buf)
13929 {
13930     char temp[MSG_SIZ+1], *p;
13931     int len;
13932
13933     len = strlen(buf);
13934     if (len > MSG_SIZ)
13935       len = MSG_SIZ;
13936
13937     strncpy(temp, buf, len);
13938     temp[len] = 0;
13939
13940     p = temp;
13941     while (*p) {
13942         if (*p == '\n' || *p == '\r')
13943           *p = ' ';
13944         ++p;
13945     }
13946
13947     strcat(temp, "\n");
13948     SendToICS(temp);
13949     SendToPlayer(temp, strlen(temp));
13950 }
13951
13952 void
13953 SetWhiteToPlayEvent ()
13954 {
13955     if (gameMode == EditPosition) {
13956         blackPlaysFirst = FALSE;
13957         DisplayBothClocks();    /* works because currentMove is 0 */
13958     } else if (gameMode == IcsExamining) {
13959         SendToICS(ics_prefix);
13960         SendToICS("tomove white\n");
13961     }
13962 }
13963
13964 void
13965 SetBlackToPlayEvent ()
13966 {
13967     if (gameMode == EditPosition) {
13968         blackPlaysFirst = TRUE;
13969         currentMove = 1;        /* kludge */
13970         DisplayBothClocks();
13971         currentMove = 0;
13972     } else if (gameMode == IcsExamining) {
13973         SendToICS(ics_prefix);
13974         SendToICS("tomove black\n");
13975     }
13976 }
13977
13978 void
13979 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13980 {
13981     char buf[MSG_SIZ];
13982     ChessSquare piece = boards[0][y][x];
13983
13984     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13985
13986     switch (selection) {
13987       case ClearBoard:
13988         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13989             SendToICS(ics_prefix);
13990             SendToICS("bsetup clear\n");
13991         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13992             SendToICS(ics_prefix);
13993             SendToICS("clearboard\n");
13994         } else {
13995             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13996                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13997                 for (y = 0; y < BOARD_HEIGHT; y++) {
13998                     if (gameMode == IcsExamining) {
13999                         if (boards[currentMove][y][x] != EmptySquare) {
14000                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14001                                     AAA + x, ONE + y);
14002                             SendToICS(buf);
14003                         }
14004                     } else {
14005                         boards[0][y][x] = p;
14006                     }
14007                 }
14008             }
14009         }
14010         if (gameMode == EditPosition) {
14011             DrawPosition(FALSE, boards[0]);
14012         }
14013         break;
14014
14015       case WhitePlay:
14016         SetWhiteToPlayEvent();
14017         break;
14018
14019       case BlackPlay:
14020         SetBlackToPlayEvent();
14021         break;
14022
14023       case EmptySquare:
14024         if (gameMode == IcsExamining) {
14025             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14026             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14027             SendToICS(buf);
14028         } else {
14029             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14030                 if(x == BOARD_LEFT-2) {
14031                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14032                     boards[0][y][1] = 0;
14033                 } else
14034                 if(x == BOARD_RGHT+1) {
14035                     if(y >= gameInfo.holdingsSize) break;
14036                     boards[0][y][BOARD_WIDTH-2] = 0;
14037                 } else break;
14038             }
14039             boards[0][y][x] = EmptySquare;
14040             DrawPosition(FALSE, boards[0]);
14041         }
14042         break;
14043
14044       case PromotePiece:
14045         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14046            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14047             selection = (ChessSquare) (PROMOTED piece);
14048         } else if(piece == EmptySquare) selection = WhiteSilver;
14049         else selection = (ChessSquare)((int)piece - 1);
14050         goto defaultlabel;
14051
14052       case DemotePiece:
14053         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14054            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14055             selection = (ChessSquare) (DEMOTED piece);
14056         } else if(piece == EmptySquare) selection = BlackSilver;
14057         else selection = (ChessSquare)((int)piece + 1);
14058         goto defaultlabel;
14059
14060       case WhiteQueen:
14061       case BlackQueen:
14062         if(gameInfo.variant == VariantShatranj ||
14063            gameInfo.variant == VariantXiangqi  ||
14064            gameInfo.variant == VariantCourier  ||
14065            gameInfo.variant == VariantMakruk     )
14066             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14067         goto defaultlabel;
14068
14069       case WhiteKing:
14070       case BlackKing:
14071         if(gameInfo.variant == VariantXiangqi)
14072             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14073         if(gameInfo.variant == VariantKnightmate)
14074             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14075       default:
14076         defaultlabel:
14077         if (gameMode == IcsExamining) {
14078             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14079             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14080                      PieceToChar(selection), AAA + x, ONE + y);
14081             SendToICS(buf);
14082         } else {
14083             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14084                 int n;
14085                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14086                     n = PieceToNumber(selection - BlackPawn);
14087                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14088                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14089                     boards[0][BOARD_HEIGHT-1-n][1]++;
14090                 } else
14091                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14092                     n = PieceToNumber(selection);
14093                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14094                     boards[0][n][BOARD_WIDTH-1] = selection;
14095                     boards[0][n][BOARD_WIDTH-2]++;
14096                 }
14097             } else
14098             boards[0][y][x] = selection;
14099             DrawPosition(TRUE, boards[0]);
14100             ClearHighlights();
14101             fromX = fromY = -1;
14102         }
14103         break;
14104     }
14105 }
14106
14107
14108 void
14109 DropMenuEvent (ChessSquare selection, int x, int y)
14110 {
14111     ChessMove moveType;
14112
14113     switch (gameMode) {
14114       case IcsPlayingWhite:
14115       case MachinePlaysBlack:
14116         if (!WhiteOnMove(currentMove)) {
14117             DisplayMoveError(_("It is Black's turn"));
14118             return;
14119         }
14120         moveType = WhiteDrop;
14121         break;
14122       case IcsPlayingBlack:
14123       case MachinePlaysWhite:
14124         if (WhiteOnMove(currentMove)) {
14125             DisplayMoveError(_("It is White's turn"));
14126             return;
14127         }
14128         moveType = BlackDrop;
14129         break;
14130       case EditGame:
14131         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14132         break;
14133       default:
14134         return;
14135     }
14136
14137     if (moveType == BlackDrop && selection < BlackPawn) {
14138       selection = (ChessSquare) ((int) selection
14139                                  + (int) BlackPawn - (int) WhitePawn);
14140     }
14141     if (boards[currentMove][y][x] != EmptySquare) {
14142         DisplayMoveError(_("That square is occupied"));
14143         return;
14144     }
14145
14146     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14147 }
14148
14149 void
14150 AcceptEvent ()
14151 {
14152     /* Accept a pending offer of any kind from opponent */
14153
14154     if (appData.icsActive) {
14155         SendToICS(ics_prefix);
14156         SendToICS("accept\n");
14157     } else if (cmailMsgLoaded) {
14158         if (currentMove == cmailOldMove &&
14159             commentList[cmailOldMove] != NULL &&
14160             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14161                    "Black offers a draw" : "White offers a draw")) {
14162             TruncateGame();
14163             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14164             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14165         } else {
14166             DisplayError(_("There is no pending offer on this move"), 0);
14167             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14168         }
14169     } else {
14170         /* Not used for offers from chess program */
14171     }
14172 }
14173
14174 void
14175 DeclineEvent ()
14176 {
14177     /* Decline a pending offer of any kind from opponent */
14178
14179     if (appData.icsActive) {
14180         SendToICS(ics_prefix);
14181         SendToICS("decline\n");
14182     } else if (cmailMsgLoaded) {
14183         if (currentMove == cmailOldMove &&
14184             commentList[cmailOldMove] != NULL &&
14185             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14186                    "Black offers a draw" : "White offers a draw")) {
14187 #ifdef NOTDEF
14188             AppendComment(cmailOldMove, "Draw declined", TRUE);
14189             DisplayComment(cmailOldMove - 1, "Draw declined");
14190 #endif /*NOTDEF*/
14191         } else {
14192             DisplayError(_("There is no pending offer on this move"), 0);
14193         }
14194     } else {
14195         /* Not used for offers from chess program */
14196     }
14197 }
14198
14199 void
14200 RematchEvent ()
14201 {
14202     /* Issue ICS rematch command */
14203     if (appData.icsActive) {
14204         SendToICS(ics_prefix);
14205         SendToICS("rematch\n");
14206     }
14207 }
14208
14209 void
14210 CallFlagEvent ()
14211 {
14212     /* Call your opponent's flag (claim a win on time) */
14213     if (appData.icsActive) {
14214         SendToICS(ics_prefix);
14215         SendToICS("flag\n");
14216     } else {
14217         switch (gameMode) {
14218           default:
14219             return;
14220           case MachinePlaysWhite:
14221             if (whiteFlag) {
14222                 if (blackFlag)
14223                   GameEnds(GameIsDrawn, "Both players ran out of time",
14224                            GE_PLAYER);
14225                 else
14226                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14227             } else {
14228                 DisplayError(_("Your opponent is not out of time"), 0);
14229             }
14230             break;
14231           case MachinePlaysBlack:
14232             if (blackFlag) {
14233                 if (whiteFlag)
14234                   GameEnds(GameIsDrawn, "Both players ran out of time",
14235                            GE_PLAYER);
14236                 else
14237                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14238             } else {
14239                 DisplayError(_("Your opponent is not out of time"), 0);
14240             }
14241             break;
14242         }
14243     }
14244 }
14245
14246 void
14247 ClockClick (int which)
14248 {       // [HGM] code moved to back-end from winboard.c
14249         if(which) { // black clock
14250           if (gameMode == EditPosition || gameMode == IcsExamining) {
14251             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14252             SetBlackToPlayEvent();
14253           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14254           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14255           } else if (shiftKey) {
14256             AdjustClock(which, -1);
14257           } else if (gameMode == IcsPlayingWhite ||
14258                      gameMode == MachinePlaysBlack) {
14259             CallFlagEvent();
14260           }
14261         } else { // white clock
14262           if (gameMode == EditPosition || gameMode == IcsExamining) {
14263             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14264             SetWhiteToPlayEvent();
14265           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14266           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14267           } else if (shiftKey) {
14268             AdjustClock(which, -1);
14269           } else if (gameMode == IcsPlayingBlack ||
14270                    gameMode == MachinePlaysWhite) {
14271             CallFlagEvent();
14272           }
14273         }
14274 }
14275
14276 void
14277 DrawEvent ()
14278 {
14279     /* Offer draw or accept pending draw offer from opponent */
14280
14281     if (appData.icsActive) {
14282         /* Note: tournament rules require draw offers to be
14283            made after you make your move but before you punch
14284            your clock.  Currently ICS doesn't let you do that;
14285            instead, you immediately punch your clock after making
14286            a move, but you can offer a draw at any time. */
14287
14288         SendToICS(ics_prefix);
14289         SendToICS("draw\n");
14290         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14291     } else if (cmailMsgLoaded) {
14292         if (currentMove == cmailOldMove &&
14293             commentList[cmailOldMove] != NULL &&
14294             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14295                    "Black offers a draw" : "White offers a draw")) {
14296             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14297             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14298         } else if (currentMove == cmailOldMove + 1) {
14299             char *offer = WhiteOnMove(cmailOldMove) ?
14300               "White offers a draw" : "Black offers a draw";
14301             AppendComment(currentMove, offer, TRUE);
14302             DisplayComment(currentMove - 1, offer);
14303             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14304         } else {
14305             DisplayError(_("You must make your move before offering a draw"), 0);
14306             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14307         }
14308     } else if (first.offeredDraw) {
14309         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14310     } else {
14311         if (first.sendDrawOffers) {
14312             SendToProgram("draw\n", &first);
14313             userOfferedDraw = TRUE;
14314         }
14315     }
14316 }
14317
14318 void
14319 AdjournEvent ()
14320 {
14321     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14322
14323     if (appData.icsActive) {
14324         SendToICS(ics_prefix);
14325         SendToICS("adjourn\n");
14326     } else {
14327         /* Currently GNU Chess doesn't offer or accept Adjourns */
14328     }
14329 }
14330
14331
14332 void
14333 AbortEvent ()
14334 {
14335     /* Offer Abort or accept pending Abort offer from opponent */
14336
14337     if (appData.icsActive) {
14338         SendToICS(ics_prefix);
14339         SendToICS("abort\n");
14340     } else {
14341         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14342     }
14343 }
14344
14345 void
14346 ResignEvent ()
14347 {
14348     /* Resign.  You can do this even if it's not your turn. */
14349
14350     if (appData.icsActive) {
14351         SendToICS(ics_prefix);
14352         SendToICS("resign\n");
14353     } else {
14354         switch (gameMode) {
14355           case MachinePlaysWhite:
14356             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14357             break;
14358           case MachinePlaysBlack:
14359             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14360             break;
14361           case EditGame:
14362             if (cmailMsgLoaded) {
14363                 TruncateGame();
14364                 if (WhiteOnMove(cmailOldMove)) {
14365                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14366                 } else {
14367                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14368                 }
14369                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14370             }
14371             break;
14372           default:
14373             break;
14374         }
14375     }
14376 }
14377
14378
14379 void
14380 StopObservingEvent ()
14381 {
14382     /* Stop observing current games */
14383     SendToICS(ics_prefix);
14384     SendToICS("unobserve\n");
14385 }
14386
14387 void
14388 StopExaminingEvent ()
14389 {
14390     /* Stop observing current game */
14391     SendToICS(ics_prefix);
14392     SendToICS("unexamine\n");
14393 }
14394
14395 void
14396 ForwardInner (int target)
14397 {
14398     int limit; int oldSeekGraphUp = seekGraphUp;
14399
14400     if (appData.debugMode)
14401         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14402                 target, currentMove, forwardMostMove);
14403
14404     if (gameMode == EditPosition)
14405       return;
14406
14407     seekGraphUp = FALSE;
14408     MarkTargetSquares(1);
14409
14410     if (gameMode == PlayFromGameFile && !pausing)
14411       PauseEvent();
14412
14413     if (gameMode == IcsExamining && pausing)
14414       limit = pauseExamForwardMostMove;
14415     else
14416       limit = forwardMostMove;
14417
14418     if (target > limit) target = limit;
14419
14420     if (target > 0 && moveList[target - 1][0]) {
14421         int fromX, fromY, toX, toY;
14422         toX = moveList[target - 1][2] - AAA;
14423         toY = moveList[target - 1][3] - ONE;
14424         if (moveList[target - 1][1] == '@') {
14425             if (appData.highlightLastMove) {
14426                 SetHighlights(-1, -1, toX, toY);
14427             }
14428         } else {
14429             fromX = moveList[target - 1][0] - AAA;
14430             fromY = moveList[target - 1][1] - ONE;
14431             if (target == currentMove + 1) {
14432                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14433             }
14434             if (appData.highlightLastMove) {
14435                 SetHighlights(fromX, fromY, toX, toY);
14436             }
14437         }
14438     }
14439     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14440         gameMode == Training || gameMode == PlayFromGameFile ||
14441         gameMode == AnalyzeFile) {
14442         while (currentMove < target) {
14443             SendMoveToProgram(currentMove++, &first);
14444         }
14445     } else {
14446         currentMove = target;
14447     }
14448
14449     if (gameMode == EditGame || gameMode == EndOfGame) {
14450         whiteTimeRemaining = timeRemaining[0][currentMove];
14451         blackTimeRemaining = timeRemaining[1][currentMove];
14452     }
14453     DisplayBothClocks();
14454     DisplayMove(currentMove - 1);
14455     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14456     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14457     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14458         DisplayComment(currentMove - 1, commentList[currentMove]);
14459     }
14460     ClearMap(); // [HGM] exclude: invalidate map
14461 }
14462
14463
14464 void
14465 ForwardEvent ()
14466 {
14467     if (gameMode == IcsExamining && !pausing) {
14468         SendToICS(ics_prefix);
14469         SendToICS("forward\n");
14470     } else {
14471         ForwardInner(currentMove + 1);
14472     }
14473 }
14474
14475 void
14476 ToEndEvent ()
14477 {
14478     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14479         /* to optimze, we temporarily turn off analysis mode while we feed
14480          * the remaining moves to the engine. Otherwise we get analysis output
14481          * after each move.
14482          */
14483         if (first.analysisSupport) {
14484           SendToProgram("exit\nforce\n", &first);
14485           first.analyzing = FALSE;
14486         }
14487     }
14488
14489     if (gameMode == IcsExamining && !pausing) {
14490         SendToICS(ics_prefix);
14491         SendToICS("forward 999999\n");
14492     } else {
14493         ForwardInner(forwardMostMove);
14494     }
14495
14496     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14497         /* we have fed all the moves, so reactivate analysis mode */
14498         SendToProgram("analyze\n", &first);
14499         first.analyzing = TRUE;
14500         /*first.maybeThinking = TRUE;*/
14501         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14502     }
14503 }
14504
14505 void
14506 BackwardInner (int target)
14507 {
14508     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14509
14510     if (appData.debugMode)
14511         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14512                 target, currentMove, forwardMostMove);
14513
14514     if (gameMode == EditPosition) return;
14515     seekGraphUp = FALSE;
14516     MarkTargetSquares(1);
14517     if (currentMove <= backwardMostMove) {
14518         ClearHighlights();
14519         DrawPosition(full_redraw, boards[currentMove]);
14520         return;
14521     }
14522     if (gameMode == PlayFromGameFile && !pausing)
14523       PauseEvent();
14524
14525     if (moveList[target][0]) {
14526         int fromX, fromY, toX, toY;
14527         toX = moveList[target][2] - AAA;
14528         toY = moveList[target][3] - ONE;
14529         if (moveList[target][1] == '@') {
14530             if (appData.highlightLastMove) {
14531                 SetHighlights(-1, -1, toX, toY);
14532             }
14533         } else {
14534             fromX = moveList[target][0] - AAA;
14535             fromY = moveList[target][1] - ONE;
14536             if (target == currentMove - 1) {
14537                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14538             }
14539             if (appData.highlightLastMove) {
14540                 SetHighlights(fromX, fromY, toX, toY);
14541             }
14542         }
14543     }
14544     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14545         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14546         while (currentMove > target) {
14547             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14548                 // null move cannot be undone. Reload program with move history before it.
14549                 int i;
14550                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14551                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14552                 }
14553                 SendBoard(&first, i); 
14554                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14555                 break;
14556             }
14557             SendToProgram("undo\n", &first);
14558             currentMove--;
14559         }
14560     } else {
14561         currentMove = target;
14562     }
14563
14564     if (gameMode == EditGame || gameMode == EndOfGame) {
14565         whiteTimeRemaining = timeRemaining[0][currentMove];
14566         blackTimeRemaining = timeRemaining[1][currentMove];
14567     }
14568     DisplayBothClocks();
14569     DisplayMove(currentMove - 1);
14570     DrawPosition(full_redraw, boards[currentMove]);
14571     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14572     // [HGM] PV info: routine tests if comment empty
14573     DisplayComment(currentMove - 1, commentList[currentMove]);
14574     ClearMap(); // [HGM] exclude: invalidate map
14575 }
14576
14577 void
14578 BackwardEvent ()
14579 {
14580     if (gameMode == IcsExamining && !pausing) {
14581         SendToICS(ics_prefix);
14582         SendToICS("backward\n");
14583     } else {
14584         BackwardInner(currentMove - 1);
14585     }
14586 }
14587
14588 void
14589 ToStartEvent ()
14590 {
14591     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14592         /* to optimize, we temporarily turn off analysis mode while we undo
14593          * all the moves. Otherwise we get analysis output after each undo.
14594          */
14595         if (first.analysisSupport) {
14596           SendToProgram("exit\nforce\n", &first);
14597           first.analyzing = FALSE;
14598         }
14599     }
14600
14601     if (gameMode == IcsExamining && !pausing) {
14602         SendToICS(ics_prefix);
14603         SendToICS("backward 999999\n");
14604     } else {
14605         BackwardInner(backwardMostMove);
14606     }
14607
14608     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14609         /* we have fed all the moves, so reactivate analysis mode */
14610         SendToProgram("analyze\n", &first);
14611         first.analyzing = TRUE;
14612         /*first.maybeThinking = TRUE;*/
14613         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14614     }
14615 }
14616
14617 void
14618 ToNrEvent (int to)
14619 {
14620   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14621   if (to >= forwardMostMove) to = forwardMostMove;
14622   if (to <= backwardMostMove) to = backwardMostMove;
14623   if (to < currentMove) {
14624     BackwardInner(to);
14625   } else {
14626     ForwardInner(to);
14627   }
14628 }
14629
14630 void
14631 RevertEvent (Boolean annotate)
14632 {
14633     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14634         return;
14635     }
14636     if (gameMode != IcsExamining) {
14637         DisplayError(_("You are not examining a game"), 0);
14638         return;
14639     }
14640     if (pausing) {
14641         DisplayError(_("You can't revert while pausing"), 0);
14642         return;
14643     }
14644     SendToICS(ics_prefix);
14645     SendToICS("revert\n");
14646 }
14647
14648 void
14649 RetractMoveEvent ()
14650 {
14651     switch (gameMode) {
14652       case MachinePlaysWhite:
14653       case MachinePlaysBlack:
14654         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14655             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14656             return;
14657         }
14658         if (forwardMostMove < 2) return;
14659         currentMove = forwardMostMove = forwardMostMove - 2;
14660         whiteTimeRemaining = timeRemaining[0][currentMove];
14661         blackTimeRemaining = timeRemaining[1][currentMove];
14662         DisplayBothClocks();
14663         DisplayMove(currentMove - 1);
14664         ClearHighlights();/*!! could figure this out*/
14665         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14666         SendToProgram("remove\n", &first);
14667         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14668         break;
14669
14670       case BeginningOfGame:
14671       default:
14672         break;
14673
14674       case IcsPlayingWhite:
14675       case IcsPlayingBlack:
14676         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14677             SendToICS(ics_prefix);
14678             SendToICS("takeback 2\n");
14679         } else {
14680             SendToICS(ics_prefix);
14681             SendToICS("takeback 1\n");
14682         }
14683         break;
14684     }
14685 }
14686
14687 void
14688 MoveNowEvent ()
14689 {
14690     ChessProgramState *cps;
14691
14692     switch (gameMode) {
14693       case MachinePlaysWhite:
14694         if (!WhiteOnMove(forwardMostMove)) {
14695             DisplayError(_("It is your turn"), 0);
14696             return;
14697         }
14698         cps = &first;
14699         break;
14700       case MachinePlaysBlack:
14701         if (WhiteOnMove(forwardMostMove)) {
14702             DisplayError(_("It is your turn"), 0);
14703             return;
14704         }
14705         cps = &first;
14706         break;
14707       case TwoMachinesPlay:
14708         if (WhiteOnMove(forwardMostMove) ==
14709             (first.twoMachinesColor[0] == 'w')) {
14710             cps = &first;
14711         } else {
14712             cps = &second;
14713         }
14714         break;
14715       case BeginningOfGame:
14716       default:
14717         return;
14718     }
14719     SendToProgram("?\n", cps);
14720 }
14721
14722 void
14723 TruncateGameEvent ()
14724 {
14725     EditGameEvent();
14726     if (gameMode != EditGame) return;
14727     TruncateGame();
14728 }
14729
14730 void
14731 TruncateGame ()
14732 {
14733     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14734     if (forwardMostMove > currentMove) {
14735         if (gameInfo.resultDetails != NULL) {
14736             free(gameInfo.resultDetails);
14737             gameInfo.resultDetails = NULL;
14738             gameInfo.result = GameUnfinished;
14739         }
14740         forwardMostMove = currentMove;
14741         HistorySet(parseList, backwardMostMove, forwardMostMove,
14742                    currentMove-1);
14743     }
14744 }
14745
14746 void
14747 HintEvent ()
14748 {
14749     if (appData.noChessProgram) return;
14750     switch (gameMode) {
14751       case MachinePlaysWhite:
14752         if (WhiteOnMove(forwardMostMove)) {
14753             DisplayError(_("Wait until your turn"), 0);
14754             return;
14755         }
14756         break;
14757       case BeginningOfGame:
14758       case MachinePlaysBlack:
14759         if (!WhiteOnMove(forwardMostMove)) {
14760             DisplayError(_("Wait until your turn"), 0);
14761             return;
14762         }
14763         break;
14764       default:
14765         DisplayError(_("No hint available"), 0);
14766         return;
14767     }
14768     SendToProgram("hint\n", &first);
14769     hintRequested = TRUE;
14770 }
14771
14772 void
14773 BookEvent ()
14774 {
14775     if (appData.noChessProgram) return;
14776     switch (gameMode) {
14777       case MachinePlaysWhite:
14778         if (WhiteOnMove(forwardMostMove)) {
14779             DisplayError(_("Wait until your turn"), 0);
14780             return;
14781         }
14782         break;
14783       case BeginningOfGame:
14784       case MachinePlaysBlack:
14785         if (!WhiteOnMove(forwardMostMove)) {
14786             DisplayError(_("Wait until your turn"), 0);
14787             return;
14788         }
14789         break;
14790       case EditPosition:
14791         EditPositionDone(TRUE);
14792         break;
14793       case TwoMachinesPlay:
14794         return;
14795       default:
14796         break;
14797     }
14798     SendToProgram("bk\n", &first);
14799     bookOutput[0] = NULLCHAR;
14800     bookRequested = TRUE;
14801 }
14802
14803 void
14804 AboutGameEvent ()
14805 {
14806     char *tags = PGNTags(&gameInfo);
14807     TagsPopUp(tags, CmailMsg());
14808     free(tags);
14809 }
14810
14811 /* end button procedures */
14812
14813 void
14814 PrintPosition (FILE *fp, int move)
14815 {
14816     int i, j;
14817
14818     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14819         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14820             char c = PieceToChar(boards[move][i][j]);
14821             fputc(c == 'x' ? '.' : c, fp);
14822             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14823         }
14824     }
14825     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14826       fprintf(fp, "white to play\n");
14827     else
14828       fprintf(fp, "black to play\n");
14829 }
14830
14831 void
14832 PrintOpponents (FILE *fp)
14833 {
14834     if (gameInfo.white != NULL) {
14835         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14836     } else {
14837         fprintf(fp, "\n");
14838     }
14839 }
14840
14841 /* Find last component of program's own name, using some heuristics */
14842 void
14843 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14844 {
14845     char *p, *q, c;
14846     int local = (strcmp(host, "localhost") == 0);
14847     while (!local && (p = strchr(prog, ';')) != NULL) {
14848         p++;
14849         while (*p == ' ') p++;
14850         prog = p;
14851     }
14852     if (*prog == '"' || *prog == '\'') {
14853         q = strchr(prog + 1, *prog);
14854     } else {
14855         q = strchr(prog, ' ');
14856     }
14857     if (q == NULL) q = prog + strlen(prog);
14858     p = q;
14859     while (p >= prog && *p != '/' && *p != '\\') p--;
14860     p++;
14861     if(p == prog && *p == '"') p++;
14862     c = *q; *q = 0;
14863     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14864     memcpy(buf, p, q - p);
14865     buf[q - p] = NULLCHAR;
14866     if (!local) {
14867         strcat(buf, "@");
14868         strcat(buf, host);
14869     }
14870 }
14871
14872 char *
14873 TimeControlTagValue ()
14874 {
14875     char buf[MSG_SIZ];
14876     if (!appData.clockMode) {
14877       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14878     } else if (movesPerSession > 0) {
14879       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14880     } else if (timeIncrement == 0) {
14881       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14882     } else {
14883       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14884     }
14885     return StrSave(buf);
14886 }
14887
14888 void
14889 SetGameInfo ()
14890 {
14891     /* This routine is used only for certain modes */
14892     VariantClass v = gameInfo.variant;
14893     ChessMove r = GameUnfinished;
14894     char *p = NULL;
14895
14896     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14897         r = gameInfo.result;
14898         p = gameInfo.resultDetails;
14899         gameInfo.resultDetails = NULL;
14900     }
14901     ClearGameInfo(&gameInfo);
14902     gameInfo.variant = v;
14903
14904     switch (gameMode) {
14905       case MachinePlaysWhite:
14906         gameInfo.event = StrSave( appData.pgnEventHeader );
14907         gameInfo.site = StrSave(HostName());
14908         gameInfo.date = PGNDate();
14909         gameInfo.round = StrSave("-");
14910         gameInfo.white = StrSave(first.tidy);
14911         gameInfo.black = StrSave(UserName());
14912         gameInfo.timeControl = TimeControlTagValue();
14913         break;
14914
14915       case MachinePlaysBlack:
14916         gameInfo.event = StrSave( appData.pgnEventHeader );
14917         gameInfo.site = StrSave(HostName());
14918         gameInfo.date = PGNDate();
14919         gameInfo.round = StrSave("-");
14920         gameInfo.white = StrSave(UserName());
14921         gameInfo.black = StrSave(first.tidy);
14922         gameInfo.timeControl = TimeControlTagValue();
14923         break;
14924
14925       case TwoMachinesPlay:
14926         gameInfo.event = StrSave( appData.pgnEventHeader );
14927         gameInfo.site = StrSave(HostName());
14928         gameInfo.date = PGNDate();
14929         if (roundNr > 0) {
14930             char buf[MSG_SIZ];
14931             snprintf(buf, MSG_SIZ, "%d", roundNr);
14932             gameInfo.round = StrSave(buf);
14933         } else {
14934             gameInfo.round = StrSave("-");
14935         }
14936         if (first.twoMachinesColor[0] == 'w') {
14937             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14938             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14939         } else {
14940             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14941             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14942         }
14943         gameInfo.timeControl = TimeControlTagValue();
14944         break;
14945
14946       case EditGame:
14947         gameInfo.event = StrSave("Edited game");
14948         gameInfo.site = StrSave(HostName());
14949         gameInfo.date = PGNDate();
14950         gameInfo.round = StrSave("-");
14951         gameInfo.white = StrSave("-");
14952         gameInfo.black = StrSave("-");
14953         gameInfo.result = r;
14954         gameInfo.resultDetails = p;
14955         break;
14956
14957       case EditPosition:
14958         gameInfo.event = StrSave("Edited position");
14959         gameInfo.site = StrSave(HostName());
14960         gameInfo.date = PGNDate();
14961         gameInfo.round = StrSave("-");
14962         gameInfo.white = StrSave("-");
14963         gameInfo.black = StrSave("-");
14964         break;
14965
14966       case IcsPlayingWhite:
14967       case IcsPlayingBlack:
14968       case IcsObserving:
14969       case IcsExamining:
14970         break;
14971
14972       case PlayFromGameFile:
14973         gameInfo.event = StrSave("Game from non-PGN file");
14974         gameInfo.site = StrSave(HostName());
14975         gameInfo.date = PGNDate();
14976         gameInfo.round = StrSave("-");
14977         gameInfo.white = StrSave("?");
14978         gameInfo.black = StrSave("?");
14979         break;
14980
14981       default:
14982         break;
14983     }
14984 }
14985
14986 void
14987 ReplaceComment (int index, char *text)
14988 {
14989     int len;
14990     char *p;
14991     float score;
14992
14993     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14994        pvInfoList[index-1].depth == len &&
14995        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14996        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14997     while (*text == '\n') text++;
14998     len = strlen(text);
14999     while (len > 0 && text[len - 1] == '\n') len--;
15000
15001     if (commentList[index] != NULL)
15002       free(commentList[index]);
15003
15004     if (len == 0) {
15005         commentList[index] = NULL;
15006         return;
15007     }
15008   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15009       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15010       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15011     commentList[index] = (char *) malloc(len + 2);
15012     strncpy(commentList[index], text, len);
15013     commentList[index][len] = '\n';
15014     commentList[index][len + 1] = NULLCHAR;
15015   } else {
15016     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15017     char *p;
15018     commentList[index] = (char *) malloc(len + 7);
15019     safeStrCpy(commentList[index], "{\n", 3);
15020     safeStrCpy(commentList[index]+2, text, len+1);
15021     commentList[index][len+2] = NULLCHAR;
15022     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15023     strcat(commentList[index], "\n}\n");
15024   }
15025 }
15026
15027 void
15028 CrushCRs (char *text)
15029 {
15030   char *p = text;
15031   char *q = text;
15032   char ch;
15033
15034   do {
15035     ch = *p++;
15036     if (ch == '\r') continue;
15037     *q++ = ch;
15038   } while (ch != '\0');
15039 }
15040
15041 void
15042 AppendComment (int index, char *text, Boolean addBraces)
15043 /* addBraces  tells if we should add {} */
15044 {
15045     int oldlen, len;
15046     char *old;
15047
15048 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15049     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15050
15051     CrushCRs(text);
15052     while (*text == '\n') text++;
15053     len = strlen(text);
15054     while (len > 0 && text[len - 1] == '\n') len--;
15055     text[len] = NULLCHAR;
15056
15057     if (len == 0) return;
15058
15059     if (commentList[index] != NULL) {
15060       Boolean addClosingBrace = addBraces;
15061         old = commentList[index];
15062         oldlen = strlen(old);
15063         while(commentList[index][oldlen-1] ==  '\n')
15064           commentList[index][--oldlen] = NULLCHAR;
15065         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15066         safeStrCpy(commentList[index], old, oldlen + len + 6);
15067         free(old);
15068         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15069         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15070           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15071           while (*text == '\n') { text++; len--; }
15072           commentList[index][--oldlen] = NULLCHAR;
15073       }
15074         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15075         else          strcat(commentList[index], "\n");
15076         strcat(commentList[index], text);
15077         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15078         else          strcat(commentList[index], "\n");
15079     } else {
15080         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15081         if(addBraces)
15082           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15083         else commentList[index][0] = NULLCHAR;
15084         strcat(commentList[index], text);
15085         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15086         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15087     }
15088 }
15089
15090 static char *
15091 FindStr (char * text, char * sub_text)
15092 {
15093     char * result = strstr( text, sub_text );
15094
15095     if( result != NULL ) {
15096         result += strlen( sub_text );
15097     }
15098
15099     return result;
15100 }
15101
15102 /* [AS] Try to extract PV info from PGN comment */
15103 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15104 char *
15105 GetInfoFromComment (int index, char * text)
15106 {
15107     char * sep = text, *p;
15108
15109     if( text != NULL && index > 0 ) {
15110         int score = 0;
15111         int depth = 0;
15112         int time = -1, sec = 0, deci;
15113         char * s_eval = FindStr( text, "[%eval " );
15114         char * s_emt = FindStr( text, "[%emt " );
15115
15116         if( s_eval != NULL || s_emt != NULL ) {
15117             /* New style */
15118             char delim;
15119
15120             if( s_eval != NULL ) {
15121                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15122                     return text;
15123                 }
15124
15125                 if( delim != ']' ) {
15126                     return text;
15127                 }
15128             }
15129
15130             if( s_emt != NULL ) {
15131             }
15132                 return text;
15133         }
15134         else {
15135             /* We expect something like: [+|-]nnn.nn/dd */
15136             int score_lo = 0;
15137
15138             if(*text != '{') return text; // [HGM] braces: must be normal comment
15139
15140             sep = strchr( text, '/' );
15141             if( sep == NULL || sep < (text+4) ) {
15142                 return text;
15143             }
15144
15145             p = text;
15146             if(p[1] == '(') { // comment starts with PV
15147                p = strchr(p, ')'); // locate end of PV
15148                if(p == NULL || sep < p+5) return text;
15149                // at this point we have something like "{(.*) +0.23/6 ..."
15150                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15151                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15152                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15153             }
15154             time = -1; sec = -1; deci = -1;
15155             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15156                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15157                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15158                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15159                 return text;
15160             }
15161
15162             if( score_lo < 0 || score_lo >= 100 ) {
15163                 return text;
15164             }
15165
15166             if(sec >= 0) time = 600*time + 10*sec; else
15167             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15168
15169             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15170
15171             /* [HGM] PV time: now locate end of PV info */
15172             while( *++sep >= '0' && *sep <= '9'); // strip depth
15173             if(time >= 0)
15174             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15175             if(sec >= 0)
15176             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15177             if(deci >= 0)
15178             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15179             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15180         }
15181
15182         if( depth <= 0 ) {
15183             return text;
15184         }
15185
15186         if( time < 0 ) {
15187             time = -1;
15188         }
15189
15190         pvInfoList[index-1].depth = depth;
15191         pvInfoList[index-1].score = score;
15192         pvInfoList[index-1].time  = 10*time; // centi-sec
15193         if(*sep == '}') *sep = 0; else *--sep = '{';
15194         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15195     }
15196     return sep;
15197 }
15198
15199 void
15200 SendToProgram (char *message, ChessProgramState *cps)
15201 {
15202     int count, outCount, error;
15203     char buf[MSG_SIZ];
15204
15205     if (cps->pr == NoProc) return;
15206     Attention(cps);
15207
15208     if (appData.debugMode) {
15209         TimeMark now;
15210         GetTimeMark(&now);
15211         fprintf(debugFP, "%ld >%-6s: %s",
15212                 SubtractTimeMarks(&now, &programStartTime),
15213                 cps->which, message);
15214         if(serverFP)
15215             fprintf(serverFP, "%ld >%-6s: %s",
15216                 SubtractTimeMarks(&now, &programStartTime),
15217                 cps->which, message), fflush(serverFP);
15218     }
15219
15220     count = strlen(message);
15221     outCount = OutputToProcess(cps->pr, message, count, &error);
15222     if (outCount < count && !exiting
15223                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15224       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15225       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15226         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15227             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15228                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15229                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15230                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15231             } else {
15232                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15233                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15234                 gameInfo.result = res;
15235             }
15236             gameInfo.resultDetails = StrSave(buf);
15237         }
15238         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15239         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15240     }
15241 }
15242
15243 void
15244 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15245 {
15246     char *end_str;
15247     char buf[MSG_SIZ];
15248     ChessProgramState *cps = (ChessProgramState *)closure;
15249
15250     if (isr != cps->isr) return; /* Killed intentionally */
15251     if (count <= 0) {
15252         if (count == 0) {
15253             RemoveInputSource(cps->isr);
15254             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15255             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15256                     _(cps->which), cps->program);
15257         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15258                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15259                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15260                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15261                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15262                 } else {
15263                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15264                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15265                     gameInfo.result = res;
15266                 }
15267                 gameInfo.resultDetails = StrSave(buf);
15268             }
15269             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15270             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15271         } else {
15272             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15273                     _(cps->which), cps->program);
15274             RemoveInputSource(cps->isr);
15275
15276             /* [AS] Program is misbehaving badly... kill it */
15277             if( count == -2 ) {
15278                 DestroyChildProcess( cps->pr, 9 );
15279                 cps->pr = NoProc;
15280             }
15281
15282             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15283         }
15284         return;
15285     }
15286
15287     if ((end_str = strchr(message, '\r')) != NULL)
15288       *end_str = NULLCHAR;
15289     if ((end_str = strchr(message, '\n')) != NULL)
15290       *end_str = NULLCHAR;
15291
15292     if (appData.debugMode) {
15293         TimeMark now; int print = 1;
15294         char *quote = ""; char c; int i;
15295
15296         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15297                 char start = message[0];
15298                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15299                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15300                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15301                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15302                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15303                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15304                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15305                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15306                    sscanf(message, "hint: %c", &c)!=1 && 
15307                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15308                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15309                     print = (appData.engineComments >= 2);
15310                 }
15311                 message[0] = start; // restore original message
15312         }
15313         if(print) {
15314                 GetTimeMark(&now);
15315                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15316                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15317                         quote,
15318                         message);
15319                 if(serverFP)
15320                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15321                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15322                         quote,
15323                         message), fflush(serverFP);
15324         }
15325     }
15326
15327     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15328     if (appData.icsEngineAnalyze) {
15329         if (strstr(message, "whisper") != NULL ||
15330              strstr(message, "kibitz") != NULL ||
15331             strstr(message, "tellics") != NULL) return;
15332     }
15333
15334     HandleMachineMove(message, cps);
15335 }
15336
15337
15338 void
15339 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15340 {
15341     char buf[MSG_SIZ];
15342     int seconds;
15343
15344     if( timeControl_2 > 0 ) {
15345         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15346             tc = timeControl_2;
15347         }
15348     }
15349     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15350     inc /= cps->timeOdds;
15351     st  /= cps->timeOdds;
15352
15353     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15354
15355     if (st > 0) {
15356       /* Set exact time per move, normally using st command */
15357       if (cps->stKludge) {
15358         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15359         seconds = st % 60;
15360         if (seconds == 0) {
15361           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15362         } else {
15363           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15364         }
15365       } else {
15366         snprintf(buf, MSG_SIZ, "st %d\n", st);
15367       }
15368     } else {
15369       /* Set conventional or incremental time control, using level command */
15370       if (seconds == 0) {
15371         /* Note old gnuchess bug -- minutes:seconds used to not work.
15372            Fixed in later versions, but still avoid :seconds
15373            when seconds is 0. */
15374         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15375       } else {
15376         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15377                  seconds, inc/1000.);
15378       }
15379     }
15380     SendToProgram(buf, cps);
15381
15382     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15383     /* Orthogonally, limit search to given depth */
15384     if (sd > 0) {
15385       if (cps->sdKludge) {
15386         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15387       } else {
15388         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15389       }
15390       SendToProgram(buf, cps);
15391     }
15392
15393     if(cps->nps >= 0) { /* [HGM] nps */
15394         if(cps->supportsNPS == FALSE)
15395           cps->nps = -1; // don't use if engine explicitly says not supported!
15396         else {
15397           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15398           SendToProgram(buf, cps);
15399         }
15400     }
15401 }
15402
15403 ChessProgramState *
15404 WhitePlayer ()
15405 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15406 {
15407     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15408        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15409         return &second;
15410     return &first;
15411 }
15412
15413 void
15414 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15415 {
15416     char message[MSG_SIZ];
15417     long time, otime;
15418
15419     /* Note: this routine must be called when the clocks are stopped
15420        or when they have *just* been set or switched; otherwise
15421        it will be off by the time since the current tick started.
15422     */
15423     if (machineWhite) {
15424         time = whiteTimeRemaining / 10;
15425         otime = blackTimeRemaining / 10;
15426     } else {
15427         time = blackTimeRemaining / 10;
15428         otime = whiteTimeRemaining / 10;
15429     }
15430     /* [HGM] translate opponent's time by time-odds factor */
15431     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15432
15433     if (time <= 0) time = 1;
15434     if (otime <= 0) otime = 1;
15435
15436     snprintf(message, MSG_SIZ, "time %ld\n", time);
15437     SendToProgram(message, cps);
15438
15439     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15440     SendToProgram(message, cps);
15441 }
15442
15443 int
15444 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15445 {
15446   char buf[MSG_SIZ];
15447   int len = strlen(name);
15448   int val;
15449
15450   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15451     (*p) += len + 1;
15452     sscanf(*p, "%d", &val);
15453     *loc = (val != 0);
15454     while (**p && **p != ' ')
15455       (*p)++;
15456     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15457     SendToProgram(buf, cps);
15458     return TRUE;
15459   }
15460   return FALSE;
15461 }
15462
15463 int
15464 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15465 {
15466   char buf[MSG_SIZ];
15467   int len = strlen(name);
15468   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15469     (*p) += len + 1;
15470     sscanf(*p, "%d", loc);
15471     while (**p && **p != ' ') (*p)++;
15472     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15473     SendToProgram(buf, cps);
15474     return TRUE;
15475   }
15476   return FALSE;
15477 }
15478
15479 int
15480 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15481 {
15482   char buf[MSG_SIZ];
15483   int len = strlen(name);
15484   if (strncmp((*p), name, len) == 0
15485       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15486     (*p) += len + 2;
15487     sscanf(*p, "%[^\"]", loc);
15488     while (**p && **p != '\"') (*p)++;
15489     if (**p == '\"') (*p)++;
15490     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15491     SendToProgram(buf, cps);
15492     return TRUE;
15493   }
15494   return FALSE;
15495 }
15496
15497 int
15498 ParseOption (Option *opt, ChessProgramState *cps)
15499 // [HGM] options: process the string that defines an engine option, and determine
15500 // name, type, default value, and allowed value range
15501 {
15502         char *p, *q, buf[MSG_SIZ];
15503         int n, min = (-1)<<31, max = 1<<31, def;
15504
15505         if(p = strstr(opt->name, " -spin ")) {
15506             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15507             if(max < min) max = min; // enforce consistency
15508             if(def < min) def = min;
15509             if(def > max) def = max;
15510             opt->value = def;
15511             opt->min = min;
15512             opt->max = max;
15513             opt->type = Spin;
15514         } else if((p = strstr(opt->name, " -slider "))) {
15515             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15516             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15517             if(max < min) max = min; // enforce consistency
15518             if(def < min) def = min;
15519             if(def > max) def = max;
15520             opt->value = def;
15521             opt->min = min;
15522             opt->max = max;
15523             opt->type = Spin; // Slider;
15524         } else if((p = strstr(opt->name, " -string "))) {
15525             opt->textValue = p+9;
15526             opt->type = TextBox;
15527         } else if((p = strstr(opt->name, " -file "))) {
15528             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15529             opt->textValue = p+7;
15530             opt->type = FileName; // FileName;
15531         } else if((p = strstr(opt->name, " -path "))) {
15532             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15533             opt->textValue = p+7;
15534             opt->type = PathName; // PathName;
15535         } else if(p = strstr(opt->name, " -check ")) {
15536             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15537             opt->value = (def != 0);
15538             opt->type = CheckBox;
15539         } else if(p = strstr(opt->name, " -combo ")) {
15540             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15541             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15542             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15543             opt->value = n = 0;
15544             while(q = StrStr(q, " /// ")) {
15545                 n++; *q = 0;    // count choices, and null-terminate each of them
15546                 q += 5;
15547                 if(*q == '*') { // remember default, which is marked with * prefix
15548                     q++;
15549                     opt->value = n;
15550                 }
15551                 cps->comboList[cps->comboCnt++] = q;
15552             }
15553             cps->comboList[cps->comboCnt++] = NULL;
15554             opt->max = n + 1;
15555             opt->type = ComboBox;
15556         } else if(p = strstr(opt->name, " -button")) {
15557             opt->type = Button;
15558         } else if(p = strstr(opt->name, " -save")) {
15559             opt->type = SaveButton;
15560         } else return FALSE;
15561         *p = 0; // terminate option name
15562         // now look if the command-line options define a setting for this engine option.
15563         if(cps->optionSettings && cps->optionSettings[0])
15564             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15565         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15566           snprintf(buf, MSG_SIZ, "option %s", p);
15567                 if(p = strstr(buf, ",")) *p = 0;
15568                 if(q = strchr(buf, '=')) switch(opt->type) {
15569                     case ComboBox:
15570                         for(n=0; n<opt->max; n++)
15571                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15572                         break;
15573                     case TextBox:
15574                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15575                         break;
15576                     case Spin:
15577                     case CheckBox:
15578                         opt->value = atoi(q+1);
15579                     default:
15580                         break;
15581                 }
15582                 strcat(buf, "\n");
15583                 SendToProgram(buf, cps);
15584         }
15585         return TRUE;
15586 }
15587
15588 void
15589 FeatureDone (ChessProgramState *cps, int val)
15590 {
15591   DelayedEventCallback cb = GetDelayedEvent();
15592   if ((cb == InitBackEnd3 && cps == &first) ||
15593       (cb == SettingsMenuIfReady && cps == &second) ||
15594       (cb == LoadEngine) ||
15595       (cb == TwoMachinesEventIfReady)) {
15596     CancelDelayedEvent();
15597     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15598   }
15599   cps->initDone = val;
15600 }
15601
15602 /* Parse feature command from engine */
15603 void
15604 ParseFeatures (char *args, ChessProgramState *cps)
15605 {
15606   char *p = args;
15607   char *q;
15608   int val;
15609   char buf[MSG_SIZ];
15610
15611   for (;;) {
15612     while (*p == ' ') p++;
15613     if (*p == NULLCHAR) return;
15614
15615     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15616     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15617     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15618     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15619     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15620     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15621     if (BoolFeature(&p, "reuse", &val, cps)) {
15622       /* Engine can disable reuse, but can't enable it if user said no */
15623       if (!val) cps->reuse = FALSE;
15624       continue;
15625     }
15626     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15627     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15628       if (gameMode == TwoMachinesPlay) {
15629         DisplayTwoMachinesTitle();
15630       } else {
15631         DisplayTitle("");
15632       }
15633       continue;
15634     }
15635     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15636     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15637     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15638     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15639     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15640     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15641     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15642     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15643     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15644     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15645     if (IntFeature(&p, "done", &val, cps)) {
15646       FeatureDone(cps, val);
15647       continue;
15648     }
15649     /* Added by Tord: */
15650     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15651     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15652     /* End of additions by Tord */
15653
15654     /* [HGM] added features: */
15655     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15656     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15657     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15658     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15659     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15660     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15661     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15662         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15663           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15664             SendToProgram(buf, cps);
15665             continue;
15666         }
15667         if(cps->nrOptions >= MAX_OPTIONS) {
15668             cps->nrOptions--;
15669             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15670             DisplayError(buf, 0);
15671         }
15672         continue;
15673     }
15674     /* End of additions by HGM */
15675
15676     /* unknown feature: complain and skip */
15677     q = p;
15678     while (*q && *q != '=') q++;
15679     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15680     SendToProgram(buf, cps);
15681     p = q;
15682     if (*p == '=') {
15683       p++;
15684       if (*p == '\"') {
15685         p++;
15686         while (*p && *p != '\"') p++;
15687         if (*p == '\"') p++;
15688       } else {
15689         while (*p && *p != ' ') p++;
15690       }
15691     }
15692   }
15693
15694 }
15695
15696 void
15697 PeriodicUpdatesEvent (int newState)
15698 {
15699     if (newState == appData.periodicUpdates)
15700       return;
15701
15702     appData.periodicUpdates=newState;
15703
15704     /* Display type changes, so update it now */
15705 //    DisplayAnalysis();
15706
15707     /* Get the ball rolling again... */
15708     if (newState) {
15709         AnalysisPeriodicEvent(1);
15710         StartAnalysisClock();
15711     }
15712 }
15713
15714 void
15715 PonderNextMoveEvent (int newState)
15716 {
15717     if (newState == appData.ponderNextMove) return;
15718     if (gameMode == EditPosition) EditPositionDone(TRUE);
15719     if (newState) {
15720         SendToProgram("hard\n", &first);
15721         if (gameMode == TwoMachinesPlay) {
15722             SendToProgram("hard\n", &second);
15723         }
15724     } else {
15725         SendToProgram("easy\n", &first);
15726         thinkOutput[0] = NULLCHAR;
15727         if (gameMode == TwoMachinesPlay) {
15728             SendToProgram("easy\n", &second);
15729         }
15730     }
15731     appData.ponderNextMove = newState;
15732 }
15733
15734 void
15735 NewSettingEvent (int option, int *feature, char *command, int value)
15736 {
15737     char buf[MSG_SIZ];
15738
15739     if (gameMode == EditPosition) EditPositionDone(TRUE);
15740     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15741     if(feature == NULL || *feature) SendToProgram(buf, &first);
15742     if (gameMode == TwoMachinesPlay) {
15743         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15744     }
15745 }
15746
15747 void
15748 ShowThinkingEvent ()
15749 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15750 {
15751     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15752     int newState = appData.showThinking
15753         // [HGM] thinking: other features now need thinking output as well
15754         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15755
15756     if (oldState == newState) return;
15757     oldState = newState;
15758     if (gameMode == EditPosition) EditPositionDone(TRUE);
15759     if (oldState) {
15760         SendToProgram("post\n", &first);
15761         if (gameMode == TwoMachinesPlay) {
15762             SendToProgram("post\n", &second);
15763         }
15764     } else {
15765         SendToProgram("nopost\n", &first);
15766         thinkOutput[0] = NULLCHAR;
15767         if (gameMode == TwoMachinesPlay) {
15768             SendToProgram("nopost\n", &second);
15769         }
15770     }
15771 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15772 }
15773
15774 void
15775 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15776 {
15777   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15778   if (pr == NoProc) return;
15779   AskQuestion(title, question, replyPrefix, pr);
15780 }
15781
15782 void
15783 TypeInEvent (char firstChar)
15784 {
15785     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15786         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15787         gameMode == AnalyzeMode || gameMode == EditGame || 
15788         gameMode == EditPosition || gameMode == IcsExamining ||
15789         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15790         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15791                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15792                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15793         gameMode == Training) PopUpMoveDialog(firstChar);
15794 }
15795
15796 void
15797 TypeInDoneEvent (char *move)
15798 {
15799         Board board;
15800         int n, fromX, fromY, toX, toY;
15801         char promoChar;
15802         ChessMove moveType;
15803
15804         // [HGM] FENedit
15805         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15806                 EditPositionPasteFEN(move);
15807                 return;
15808         }
15809         // [HGM] movenum: allow move number to be typed in any mode
15810         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15811           ToNrEvent(2*n-1);
15812           return;
15813         }
15814         // undocumented kludge: allow command-line option to be typed in!
15815         // (potentially fatal, and does not implement the effect of the option.)
15816         // should only be used for options that are values on which future decisions will be made,
15817         // and definitely not on options that would be used during initialization.
15818         if(strstr(move, "!!! -") == move) {
15819             ParseArgsFromString(move+4);
15820             return;
15821         }
15822
15823       if (gameMode != EditGame && currentMove != forwardMostMove && 
15824         gameMode != Training) {
15825         DisplayMoveError(_("Displayed move is not current"));
15826       } else {
15827         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15828           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15829         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15830         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15831           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15832           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15833         } else {
15834           DisplayMoveError(_("Could not parse move"));
15835         }
15836       }
15837 }
15838
15839 void
15840 DisplayMove (int moveNumber)
15841 {
15842     char message[MSG_SIZ];
15843     char res[MSG_SIZ];
15844     char cpThinkOutput[MSG_SIZ];
15845
15846     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15847
15848     if (moveNumber == forwardMostMove - 1 ||
15849         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15850
15851         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15852
15853         if (strchr(cpThinkOutput, '\n')) {
15854             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15855         }
15856     } else {
15857         *cpThinkOutput = NULLCHAR;
15858     }
15859
15860     /* [AS] Hide thinking from human user */
15861     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15862         *cpThinkOutput = NULLCHAR;
15863         if( thinkOutput[0] != NULLCHAR ) {
15864             int i;
15865
15866             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15867                 cpThinkOutput[i] = '.';
15868             }
15869             cpThinkOutput[i] = NULLCHAR;
15870             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15871         }
15872     }
15873
15874     if (moveNumber == forwardMostMove - 1 &&
15875         gameInfo.resultDetails != NULL) {
15876         if (gameInfo.resultDetails[0] == NULLCHAR) {
15877           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15878         } else {
15879           snprintf(res, MSG_SIZ, " {%s} %s",
15880                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15881         }
15882     } else {
15883         res[0] = NULLCHAR;
15884     }
15885
15886     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15887         DisplayMessage(res, cpThinkOutput);
15888     } else {
15889       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15890                 WhiteOnMove(moveNumber) ? " " : ".. ",
15891                 parseList[moveNumber], res);
15892         DisplayMessage(message, cpThinkOutput);
15893     }
15894 }
15895
15896 void
15897 DisplayComment (int moveNumber, char *text)
15898 {
15899     char title[MSG_SIZ];
15900
15901     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15902       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15903     } else {
15904       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15905               WhiteOnMove(moveNumber) ? " " : ".. ",
15906               parseList[moveNumber]);
15907     }
15908     if (text != NULL && (appData.autoDisplayComment || commentUp))
15909         CommentPopUp(title, text);
15910 }
15911
15912 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15913  * might be busy thinking or pondering.  It can be omitted if your
15914  * gnuchess is configured to stop thinking immediately on any user
15915  * input.  However, that gnuchess feature depends on the FIONREAD
15916  * ioctl, which does not work properly on some flavors of Unix.
15917  */
15918 void
15919 Attention (ChessProgramState *cps)
15920 {
15921 #if ATTENTION
15922     if (!cps->useSigint) return;
15923     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15924     switch (gameMode) {
15925       case MachinePlaysWhite:
15926       case MachinePlaysBlack:
15927       case TwoMachinesPlay:
15928       case IcsPlayingWhite:
15929       case IcsPlayingBlack:
15930       case AnalyzeMode:
15931       case AnalyzeFile:
15932         /* Skip if we know it isn't thinking */
15933         if (!cps->maybeThinking) return;
15934         if (appData.debugMode)
15935           fprintf(debugFP, "Interrupting %s\n", cps->which);
15936         InterruptChildProcess(cps->pr);
15937         cps->maybeThinking = FALSE;
15938         break;
15939       default:
15940         break;
15941     }
15942 #endif /*ATTENTION*/
15943 }
15944
15945 int
15946 CheckFlags ()
15947 {
15948     if (whiteTimeRemaining <= 0) {
15949         if (!whiteFlag) {
15950             whiteFlag = TRUE;
15951             if (appData.icsActive) {
15952                 if (appData.autoCallFlag &&
15953                     gameMode == IcsPlayingBlack && !blackFlag) {
15954                   SendToICS(ics_prefix);
15955                   SendToICS("flag\n");
15956                 }
15957             } else {
15958                 if (blackFlag) {
15959                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15960                 } else {
15961                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15962                     if (appData.autoCallFlag) {
15963                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15964                         return TRUE;
15965                     }
15966                 }
15967             }
15968         }
15969     }
15970     if (blackTimeRemaining <= 0) {
15971         if (!blackFlag) {
15972             blackFlag = TRUE;
15973             if (appData.icsActive) {
15974                 if (appData.autoCallFlag &&
15975                     gameMode == IcsPlayingWhite && !whiteFlag) {
15976                   SendToICS(ics_prefix);
15977                   SendToICS("flag\n");
15978                 }
15979             } else {
15980                 if (whiteFlag) {
15981                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15982                 } else {
15983                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15984                     if (appData.autoCallFlag) {
15985                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15986                         return TRUE;
15987                     }
15988                 }
15989             }
15990         }
15991     }
15992     return FALSE;
15993 }
15994
15995 void
15996 CheckTimeControl ()
15997 {
15998     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15999         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16000
16001     /*
16002      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16003      */
16004     if ( !WhiteOnMove(forwardMostMove) ) {
16005         /* White made time control */
16006         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16007         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16008         /* [HGM] time odds: correct new time quota for time odds! */
16009                                             / WhitePlayer()->timeOdds;
16010         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16011     } else {
16012         lastBlack -= blackTimeRemaining;
16013         /* Black made time control */
16014         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16015                                             / WhitePlayer()->other->timeOdds;
16016         lastWhite = whiteTimeRemaining;
16017     }
16018 }
16019
16020 void
16021 DisplayBothClocks ()
16022 {
16023     int wom = gameMode == EditPosition ?
16024       !blackPlaysFirst : WhiteOnMove(currentMove);
16025     DisplayWhiteClock(whiteTimeRemaining, wom);
16026     DisplayBlackClock(blackTimeRemaining, !wom);
16027 }
16028
16029
16030 /* Timekeeping seems to be a portability nightmare.  I think everyone
16031    has ftime(), but I'm really not sure, so I'm including some ifdefs
16032    to use other calls if you don't.  Clocks will be less accurate if
16033    you have neither ftime nor gettimeofday.
16034 */
16035
16036 /* VS 2008 requires the #include outside of the function */
16037 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16038 #include <sys/timeb.h>
16039 #endif
16040
16041 /* Get the current time as a TimeMark */
16042 void
16043 GetTimeMark (TimeMark *tm)
16044 {
16045 #if HAVE_GETTIMEOFDAY
16046
16047     struct timeval timeVal;
16048     struct timezone timeZone;
16049
16050     gettimeofday(&timeVal, &timeZone);
16051     tm->sec = (long) timeVal.tv_sec;
16052     tm->ms = (int) (timeVal.tv_usec / 1000L);
16053
16054 #else /*!HAVE_GETTIMEOFDAY*/
16055 #if HAVE_FTIME
16056
16057 // include <sys/timeb.h> / moved to just above start of function
16058     struct timeb timeB;
16059
16060     ftime(&timeB);
16061     tm->sec = (long) timeB.time;
16062     tm->ms = (int) timeB.millitm;
16063
16064 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16065     tm->sec = (long) time(NULL);
16066     tm->ms = 0;
16067 #endif
16068 #endif
16069 }
16070
16071 /* Return the difference in milliseconds between two
16072    time marks.  We assume the difference will fit in a long!
16073 */
16074 long
16075 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16076 {
16077     return 1000L*(tm2->sec - tm1->sec) +
16078            (long) (tm2->ms - tm1->ms);
16079 }
16080
16081
16082 /*
16083  * Code to manage the game clocks.
16084  *
16085  * In tournament play, black starts the clock and then white makes a move.
16086  * We give the human user a slight advantage if he is playing white---the
16087  * clocks don't run until he makes his first move, so it takes zero time.
16088  * Also, we don't account for network lag, so we could get out of sync
16089  * with GNU Chess's clock -- but then, referees are always right.
16090  */
16091
16092 static TimeMark tickStartTM;
16093 static long intendedTickLength;
16094
16095 long
16096 NextTickLength (long timeRemaining)
16097 {
16098     long nominalTickLength, nextTickLength;
16099
16100     if (timeRemaining > 0L && timeRemaining <= 10000L)
16101       nominalTickLength = 100L;
16102     else
16103       nominalTickLength = 1000L;
16104     nextTickLength = timeRemaining % nominalTickLength;
16105     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16106
16107     return nextTickLength;
16108 }
16109
16110 /* Adjust clock one minute up or down */
16111 void
16112 AdjustClock (Boolean which, int dir)
16113 {
16114     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16115     if(which) blackTimeRemaining += 60000*dir;
16116     else      whiteTimeRemaining += 60000*dir;
16117     DisplayBothClocks();
16118     adjustedClock = TRUE;
16119 }
16120
16121 /* Stop clocks and reset to a fresh time control */
16122 void
16123 ResetClocks ()
16124 {
16125     (void) StopClockTimer();
16126     if (appData.icsActive) {
16127         whiteTimeRemaining = blackTimeRemaining = 0;
16128     } else if (searchTime) {
16129         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16130         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16131     } else { /* [HGM] correct new time quote for time odds */
16132         whiteTC = blackTC = fullTimeControlString;
16133         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16134         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16135     }
16136     if (whiteFlag || blackFlag) {
16137         DisplayTitle("");
16138         whiteFlag = blackFlag = FALSE;
16139     }
16140     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16141     DisplayBothClocks();
16142     adjustedClock = FALSE;
16143 }
16144
16145 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16146
16147 /* Decrement running clock by amount of time that has passed */
16148 void
16149 DecrementClocks ()
16150 {
16151     long timeRemaining;
16152     long lastTickLength, fudge;
16153     TimeMark now;
16154
16155     if (!appData.clockMode) return;
16156     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16157
16158     GetTimeMark(&now);
16159
16160     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16161
16162     /* Fudge if we woke up a little too soon */
16163     fudge = intendedTickLength - lastTickLength;
16164     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16165
16166     if (WhiteOnMove(forwardMostMove)) {
16167         if(whiteNPS >= 0) lastTickLength = 0;
16168         timeRemaining = whiteTimeRemaining -= lastTickLength;
16169         if(timeRemaining < 0 && !appData.icsActive) {
16170             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16171             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16172                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16173                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16174             }
16175         }
16176         DisplayWhiteClock(whiteTimeRemaining - fudge,
16177                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16178     } else {
16179         if(blackNPS >= 0) lastTickLength = 0;
16180         timeRemaining = blackTimeRemaining -= lastTickLength;
16181         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16182             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16183             if(suddenDeath) {
16184                 blackStartMove = forwardMostMove;
16185                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16186             }
16187         }
16188         DisplayBlackClock(blackTimeRemaining - fudge,
16189                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16190     }
16191     if (CheckFlags()) return;
16192
16193     tickStartTM = now;
16194     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16195     StartClockTimer(intendedTickLength);
16196
16197     /* if the time remaining has fallen below the alarm threshold, sound the
16198      * alarm. if the alarm has sounded and (due to a takeback or time control
16199      * with increment) the time remaining has increased to a level above the
16200      * threshold, reset the alarm so it can sound again.
16201      */
16202
16203     if (appData.icsActive && appData.icsAlarm) {
16204
16205         /* make sure we are dealing with the user's clock */
16206         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16207                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16208            )) return;
16209
16210         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16211             alarmSounded = FALSE;
16212         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16213             PlayAlarmSound();
16214             alarmSounded = TRUE;
16215         }
16216     }
16217 }
16218
16219
16220 /* A player has just moved, so stop the previously running
16221    clock and (if in clock mode) start the other one.
16222    We redisplay both clocks in case we're in ICS mode, because
16223    ICS gives us an update to both clocks after every move.
16224    Note that this routine is called *after* forwardMostMove
16225    is updated, so the last fractional tick must be subtracted
16226    from the color that is *not* on move now.
16227 */
16228 void
16229 SwitchClocks (int newMoveNr)
16230 {
16231     long lastTickLength;
16232     TimeMark now;
16233     int flagged = FALSE;
16234
16235     GetTimeMark(&now);
16236
16237     if (StopClockTimer() && appData.clockMode) {
16238         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16239         if (!WhiteOnMove(forwardMostMove)) {
16240             if(blackNPS >= 0) lastTickLength = 0;
16241             blackTimeRemaining -= lastTickLength;
16242            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16243 //         if(pvInfoList[forwardMostMove].time == -1)
16244                  pvInfoList[forwardMostMove].time =               // use GUI time
16245                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16246         } else {
16247            if(whiteNPS >= 0) lastTickLength = 0;
16248            whiteTimeRemaining -= lastTickLength;
16249            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16250 //         if(pvInfoList[forwardMostMove].time == -1)
16251                  pvInfoList[forwardMostMove].time =
16252                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16253         }
16254         flagged = CheckFlags();
16255     }
16256     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16257     CheckTimeControl();
16258
16259     if (flagged || !appData.clockMode) return;
16260
16261     switch (gameMode) {
16262       case MachinePlaysBlack:
16263       case MachinePlaysWhite:
16264       case BeginningOfGame:
16265         if (pausing) return;
16266         break;
16267
16268       case EditGame:
16269       case PlayFromGameFile:
16270       case IcsExamining:
16271         return;
16272
16273       default:
16274         break;
16275     }
16276
16277     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16278         if(WhiteOnMove(forwardMostMove))
16279              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16280         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16281     }
16282
16283     tickStartTM = now;
16284     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16285       whiteTimeRemaining : blackTimeRemaining);
16286     StartClockTimer(intendedTickLength);
16287 }
16288
16289
16290 /* Stop both clocks */
16291 void
16292 StopClocks ()
16293 {
16294     long lastTickLength;
16295     TimeMark now;
16296
16297     if (!StopClockTimer()) return;
16298     if (!appData.clockMode) return;
16299
16300     GetTimeMark(&now);
16301
16302     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16303     if (WhiteOnMove(forwardMostMove)) {
16304         if(whiteNPS >= 0) lastTickLength = 0;
16305         whiteTimeRemaining -= lastTickLength;
16306         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16307     } else {
16308         if(blackNPS >= 0) lastTickLength = 0;
16309         blackTimeRemaining -= lastTickLength;
16310         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16311     }
16312     CheckFlags();
16313 }
16314
16315 /* Start clock of player on move.  Time may have been reset, so
16316    if clock is already running, stop and restart it. */
16317 void
16318 StartClocks ()
16319 {
16320     (void) StopClockTimer(); /* in case it was running already */
16321     DisplayBothClocks();
16322     if (CheckFlags()) return;
16323
16324     if (!appData.clockMode) return;
16325     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16326
16327     GetTimeMark(&tickStartTM);
16328     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16329       whiteTimeRemaining : blackTimeRemaining);
16330
16331    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16332     whiteNPS = blackNPS = -1;
16333     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16334        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16335         whiteNPS = first.nps;
16336     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16337        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16338         blackNPS = first.nps;
16339     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16340         whiteNPS = second.nps;
16341     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16342         blackNPS = second.nps;
16343     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16344
16345     StartClockTimer(intendedTickLength);
16346 }
16347
16348 char *
16349 TimeString (long ms)
16350 {
16351     long second, minute, hour, day;
16352     char *sign = "";
16353     static char buf[32];
16354
16355     if (ms > 0 && ms <= 9900) {
16356       /* convert milliseconds to tenths, rounding up */
16357       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16358
16359       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16360       return buf;
16361     }
16362
16363     /* convert milliseconds to seconds, rounding up */
16364     /* use floating point to avoid strangeness of integer division
16365        with negative dividends on many machines */
16366     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16367
16368     if (second < 0) {
16369         sign = "-";
16370         second = -second;
16371     }
16372
16373     day = second / (60 * 60 * 24);
16374     second = second % (60 * 60 * 24);
16375     hour = second / (60 * 60);
16376     second = second % (60 * 60);
16377     minute = second / 60;
16378     second = second % 60;
16379
16380     if (day > 0)
16381       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16382               sign, day, hour, minute, second);
16383     else if (hour > 0)
16384       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16385     else
16386       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16387
16388     return buf;
16389 }
16390
16391
16392 /*
16393  * This is necessary because some C libraries aren't ANSI C compliant yet.
16394  */
16395 char *
16396 StrStr (char *string, char *match)
16397 {
16398     int i, length;
16399
16400     length = strlen(match);
16401
16402     for (i = strlen(string) - length; i >= 0; i--, string++)
16403       if (!strncmp(match, string, length))
16404         return string;
16405
16406     return NULL;
16407 }
16408
16409 char *
16410 StrCaseStr (char *string, char *match)
16411 {
16412     int i, j, length;
16413
16414     length = strlen(match);
16415
16416     for (i = strlen(string) - length; i >= 0; i--, string++) {
16417         for (j = 0; j < length; j++) {
16418             if (ToLower(match[j]) != ToLower(string[j]))
16419               break;
16420         }
16421         if (j == length) return string;
16422     }
16423
16424     return NULL;
16425 }
16426
16427 #ifndef _amigados
16428 int
16429 StrCaseCmp (char *s1, char *s2)
16430 {
16431     char c1, c2;
16432
16433     for (;;) {
16434         c1 = ToLower(*s1++);
16435         c2 = ToLower(*s2++);
16436         if (c1 > c2) return 1;
16437         if (c1 < c2) return -1;
16438         if (c1 == NULLCHAR) return 0;
16439     }
16440 }
16441
16442
16443 int
16444 ToLower (int c)
16445 {
16446     return isupper(c) ? tolower(c) : c;
16447 }
16448
16449
16450 int
16451 ToUpper (int c)
16452 {
16453     return islower(c) ? toupper(c) : c;
16454 }
16455 #endif /* !_amigados    */
16456
16457 char *
16458 StrSave (char *s)
16459 {
16460   char *ret;
16461
16462   if ((ret = (char *) malloc(strlen(s) + 1)))
16463     {
16464       safeStrCpy(ret, s, strlen(s)+1);
16465     }
16466   return ret;
16467 }
16468
16469 char *
16470 StrSavePtr (char *s, char **savePtr)
16471 {
16472     if (*savePtr) {
16473         free(*savePtr);
16474     }
16475     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16476       safeStrCpy(*savePtr, s, strlen(s)+1);
16477     }
16478     return(*savePtr);
16479 }
16480
16481 char *
16482 PGNDate ()
16483 {
16484     time_t clock;
16485     struct tm *tm;
16486     char buf[MSG_SIZ];
16487
16488     clock = time((time_t *)NULL);
16489     tm = localtime(&clock);
16490     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16491             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16492     return StrSave(buf);
16493 }
16494
16495
16496 char *
16497 PositionToFEN (int move, char *overrideCastling)
16498 {
16499     int i, j, fromX, fromY, toX, toY;
16500     int whiteToPlay;
16501     char buf[MSG_SIZ];
16502     char *p, *q;
16503     int emptycount;
16504     ChessSquare piece;
16505
16506     whiteToPlay = (gameMode == EditPosition) ?
16507       !blackPlaysFirst : (move % 2 == 0);
16508     p = buf;
16509
16510     /* Piece placement data */
16511     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16512         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16513         emptycount = 0;
16514         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16515             if (boards[move][i][j] == EmptySquare) {
16516                 emptycount++;
16517             } else { ChessSquare piece = boards[move][i][j];
16518                 if (emptycount > 0) {
16519                     if(emptycount<10) /* [HGM] can be >= 10 */
16520                         *p++ = '0' + emptycount;
16521                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16522                     emptycount = 0;
16523                 }
16524                 if(PieceToChar(piece) == '+') {
16525                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16526                     *p++ = '+';
16527                     piece = (ChessSquare)(DEMOTED piece);
16528                 }
16529                 *p++ = PieceToChar(piece);
16530                 if(p[-1] == '~') {
16531                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16532                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16533                     *p++ = '~';
16534                 }
16535             }
16536         }
16537         if (emptycount > 0) {
16538             if(emptycount<10) /* [HGM] can be >= 10 */
16539                 *p++ = '0' + emptycount;
16540             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16541             emptycount = 0;
16542         }
16543         *p++ = '/';
16544     }
16545     *(p - 1) = ' ';
16546
16547     /* [HGM] print Crazyhouse or Shogi holdings */
16548     if( gameInfo.holdingsWidth ) {
16549         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16550         q = p;
16551         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16552             piece = boards[move][i][BOARD_WIDTH-1];
16553             if( piece != EmptySquare )
16554               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16555                   *p++ = PieceToChar(piece);
16556         }
16557         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16558             piece = boards[move][BOARD_HEIGHT-i-1][0];
16559             if( piece != EmptySquare )
16560               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16561                   *p++ = PieceToChar(piece);
16562         }
16563
16564         if( q == p ) *p++ = '-';
16565         *p++ = ']';
16566         *p++ = ' ';
16567     }
16568
16569     /* Active color */
16570     *p++ = whiteToPlay ? 'w' : 'b';
16571     *p++ = ' ';
16572
16573   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16574     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16575   } else {
16576   if(nrCastlingRights) {
16577      q = p;
16578      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16579        /* [HGM] write directly from rights */
16580            if(boards[move][CASTLING][2] != NoRights &&
16581               boards[move][CASTLING][0] != NoRights   )
16582                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16583            if(boards[move][CASTLING][2] != NoRights &&
16584               boards[move][CASTLING][1] != NoRights   )
16585                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16586            if(boards[move][CASTLING][5] != NoRights &&
16587               boards[move][CASTLING][3] != NoRights   )
16588                 *p++ = boards[move][CASTLING][3] + AAA;
16589            if(boards[move][CASTLING][5] != NoRights &&
16590               boards[move][CASTLING][4] != NoRights   )
16591                 *p++ = boards[move][CASTLING][4] + AAA;
16592      } else {
16593
16594         /* [HGM] write true castling rights */
16595         if( nrCastlingRights == 6 ) {
16596             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16597                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16598             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16599                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16600             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16601                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16602             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16603                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16604         }
16605      }
16606      if (q == p) *p++ = '-'; /* No castling rights */
16607      *p++ = ' ';
16608   }
16609
16610   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16611      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16612     /* En passant target square */
16613     if (move > backwardMostMove) {
16614         fromX = moveList[move - 1][0] - AAA;
16615         fromY = moveList[move - 1][1] - ONE;
16616         toX = moveList[move - 1][2] - AAA;
16617         toY = moveList[move - 1][3] - ONE;
16618         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16619             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16620             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16621             fromX == toX) {
16622             /* 2-square pawn move just happened */
16623             *p++ = toX + AAA;
16624             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16625         } else {
16626             *p++ = '-';
16627         }
16628     } else if(move == backwardMostMove) {
16629         // [HGM] perhaps we should always do it like this, and forget the above?
16630         if((signed char)boards[move][EP_STATUS] >= 0) {
16631             *p++ = boards[move][EP_STATUS] + AAA;
16632             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16633         } else {
16634             *p++ = '-';
16635         }
16636     } else {
16637         *p++ = '-';
16638     }
16639     *p++ = ' ';
16640   }
16641   }
16642
16643     /* [HGM] find reversible plies */
16644     {   int i = 0, j=move;
16645
16646         if (appData.debugMode) { int k;
16647             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16648             for(k=backwardMostMove; k<=forwardMostMove; k++)
16649                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16650
16651         }
16652
16653         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16654         if( j == backwardMostMove ) i += initialRulePlies;
16655         sprintf(p, "%d ", i);
16656         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16657     }
16658     /* Fullmove number */
16659     sprintf(p, "%d", (move / 2) + 1);
16660
16661     return StrSave(buf);
16662 }
16663
16664 Boolean
16665 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16666 {
16667     int i, j;
16668     char *p, c;
16669     int emptycount;
16670     ChessSquare piece;
16671
16672     p = fen;
16673
16674     /* [HGM] by default clear Crazyhouse holdings, if present */
16675     if(gameInfo.holdingsWidth) {
16676        for(i=0; i<BOARD_HEIGHT; i++) {
16677            board[i][0]             = EmptySquare; /* black holdings */
16678            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16679            board[i][1]             = (ChessSquare) 0; /* black counts */
16680            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16681        }
16682     }
16683
16684     /* Piece placement data */
16685     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16686         j = 0;
16687         for (;;) {
16688             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16689                 if (*p == '/') p++;
16690                 emptycount = gameInfo.boardWidth - j;
16691                 while (emptycount--)
16692                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16693                 break;
16694 #if(BOARD_FILES >= 10)
16695             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16696                 p++; emptycount=10;
16697                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16698                 while (emptycount--)
16699                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16700 #endif
16701             } else if (isdigit(*p)) {
16702                 emptycount = *p++ - '0';
16703                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16704                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16705                 while (emptycount--)
16706                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16707             } else if (*p == '+' || isalpha(*p)) {
16708                 if (j >= gameInfo.boardWidth) return FALSE;
16709                 if(*p=='+') {
16710                     piece = CharToPiece(*++p);
16711                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16712                     piece = (ChessSquare) (PROMOTED piece ); p++;
16713                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16714                 } else piece = CharToPiece(*p++);
16715
16716                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16717                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16718                     piece = (ChessSquare) (PROMOTED piece);
16719                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16720                     p++;
16721                 }
16722                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16723             } else {
16724                 return FALSE;
16725             }
16726         }
16727     }
16728     while (*p == '/' || *p == ' ') p++;
16729
16730     /* [HGM] look for Crazyhouse holdings here */
16731     while(*p==' ') p++;
16732     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16733         if(*p == '[') p++;
16734         if(*p == '-' ) p++; /* empty holdings */ else {
16735             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16736             /* if we would allow FEN reading to set board size, we would   */
16737             /* have to add holdings and shift the board read so far here   */
16738             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16739                 p++;
16740                 if((int) piece >= (int) BlackPawn ) {
16741                     i = (int)piece - (int)BlackPawn;
16742                     i = PieceToNumber((ChessSquare)i);
16743                     if( i >= gameInfo.holdingsSize ) return FALSE;
16744                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16745                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16746                 } else {
16747                     i = (int)piece - (int)WhitePawn;
16748                     i = PieceToNumber((ChessSquare)i);
16749                     if( i >= gameInfo.holdingsSize ) return FALSE;
16750                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16751                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16752                 }
16753             }
16754         }
16755         if(*p == ']') p++;
16756     }
16757
16758     while(*p == ' ') p++;
16759
16760     /* Active color */
16761     c = *p++;
16762     if(appData.colorNickNames) {
16763       if( c == appData.colorNickNames[0] ) c = 'w'; else
16764       if( c == appData.colorNickNames[1] ) c = 'b';
16765     }
16766     switch (c) {
16767       case 'w':
16768         *blackPlaysFirst = FALSE;
16769         break;
16770       case 'b':
16771         *blackPlaysFirst = TRUE;
16772         break;
16773       default:
16774         return FALSE;
16775     }
16776
16777     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16778     /* return the extra info in global variiables             */
16779
16780     /* set defaults in case FEN is incomplete */
16781     board[EP_STATUS] = EP_UNKNOWN;
16782     for(i=0; i<nrCastlingRights; i++ ) {
16783         board[CASTLING][i] =
16784             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16785     }   /* assume possible unless obviously impossible */
16786     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16787     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16788     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16789                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16790     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16791     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16792     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16793                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16794     FENrulePlies = 0;
16795
16796     while(*p==' ') p++;
16797     if(nrCastlingRights) {
16798       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16799           /* castling indicator present, so default becomes no castlings */
16800           for(i=0; i<nrCastlingRights; i++ ) {
16801                  board[CASTLING][i] = NoRights;
16802           }
16803       }
16804       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16805              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16806              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16807              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16808         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16809
16810         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16811             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16812             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16813         }
16814         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16815             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16816         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16817                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16818         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16819                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16820         switch(c) {
16821           case'K':
16822               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16823               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16824               board[CASTLING][2] = whiteKingFile;
16825               break;
16826           case'Q':
16827               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16828               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16829               board[CASTLING][2] = whiteKingFile;
16830               break;
16831           case'k':
16832               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16833               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16834               board[CASTLING][5] = blackKingFile;
16835               break;
16836           case'q':
16837               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16838               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16839               board[CASTLING][5] = blackKingFile;
16840           case '-':
16841               break;
16842           default: /* FRC castlings */
16843               if(c >= 'a') { /* black rights */
16844                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16845                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16846                   if(i == BOARD_RGHT) break;
16847                   board[CASTLING][5] = i;
16848                   c -= AAA;
16849                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16850                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16851                   if(c > i)
16852                       board[CASTLING][3] = c;
16853                   else
16854                       board[CASTLING][4] = c;
16855               } else { /* white rights */
16856                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16857                     if(board[0][i] == WhiteKing) break;
16858                   if(i == BOARD_RGHT) break;
16859                   board[CASTLING][2] = i;
16860                   c -= AAA - 'a' + 'A';
16861                   if(board[0][c] >= WhiteKing) break;
16862                   if(c > i)
16863                       board[CASTLING][0] = c;
16864                   else
16865                       board[CASTLING][1] = c;
16866               }
16867         }
16868       }
16869       for(i=0; i<nrCastlingRights; i++)
16870         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16871     if (appData.debugMode) {
16872         fprintf(debugFP, "FEN castling rights:");
16873         for(i=0; i<nrCastlingRights; i++)
16874         fprintf(debugFP, " %d", board[CASTLING][i]);
16875         fprintf(debugFP, "\n");
16876     }
16877
16878       while(*p==' ') p++;
16879     }
16880
16881     /* read e.p. field in games that know e.p. capture */
16882     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16883        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16884       if(*p=='-') {
16885         p++; board[EP_STATUS] = EP_NONE;
16886       } else {
16887          char c = *p++ - AAA;
16888
16889          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16890          if(*p >= '0' && *p <='9') p++;
16891          board[EP_STATUS] = c;
16892       }
16893     }
16894
16895
16896     if(sscanf(p, "%d", &i) == 1) {
16897         FENrulePlies = i; /* 50-move ply counter */
16898         /* (The move number is still ignored)    */
16899     }
16900
16901     return TRUE;
16902 }
16903
16904 void
16905 EditPositionPasteFEN (char *fen)
16906 {
16907   if (fen != NULL) {
16908     Board initial_position;
16909
16910     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16911       DisplayError(_("Bad FEN position in clipboard"), 0);
16912       return ;
16913     } else {
16914       int savedBlackPlaysFirst = blackPlaysFirst;
16915       EditPositionEvent();
16916       blackPlaysFirst = savedBlackPlaysFirst;
16917       CopyBoard(boards[0], initial_position);
16918       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16919       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16920       DisplayBothClocks();
16921       DrawPosition(FALSE, boards[currentMove]);
16922     }
16923   }
16924 }
16925
16926 static char cseq[12] = "\\   ";
16927
16928 Boolean
16929 set_cont_sequence (char *new_seq)
16930 {
16931     int len;
16932     Boolean ret;
16933
16934     // handle bad attempts to set the sequence
16935         if (!new_seq)
16936                 return 0; // acceptable error - no debug
16937
16938     len = strlen(new_seq);
16939     ret = (len > 0) && (len < sizeof(cseq));
16940     if (ret)
16941       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16942     else if (appData.debugMode)
16943       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16944     return ret;
16945 }
16946
16947 /*
16948     reformat a source message so words don't cross the width boundary.  internal
16949     newlines are not removed.  returns the wrapped size (no null character unless
16950     included in source message).  If dest is NULL, only calculate the size required
16951     for the dest buffer.  lp argument indicats line position upon entry, and it's
16952     passed back upon exit.
16953 */
16954 int
16955 wrap (char *dest, char *src, int count, int width, int *lp)
16956 {
16957     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16958
16959     cseq_len = strlen(cseq);
16960     old_line = line = *lp;
16961     ansi = len = clen = 0;
16962
16963     for (i=0; i < count; i++)
16964     {
16965         if (src[i] == '\033')
16966             ansi = 1;
16967
16968         // if we hit the width, back up
16969         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16970         {
16971             // store i & len in case the word is too long
16972             old_i = i, old_len = len;
16973
16974             // find the end of the last word
16975             while (i && src[i] != ' ' && src[i] != '\n')
16976             {
16977                 i--;
16978                 len--;
16979             }
16980
16981             // word too long?  restore i & len before splitting it
16982             if ((old_i-i+clen) >= width)
16983             {
16984                 i = old_i;
16985                 len = old_len;
16986             }
16987
16988             // extra space?
16989             if (i && src[i-1] == ' ')
16990                 len--;
16991
16992             if (src[i] != ' ' && src[i] != '\n')
16993             {
16994                 i--;
16995                 if (len)
16996                     len--;
16997             }
16998
16999             // now append the newline and continuation sequence
17000             if (dest)
17001                 dest[len] = '\n';
17002             len++;
17003             if (dest)
17004                 strncpy(dest+len, cseq, cseq_len);
17005             len += cseq_len;
17006             line = cseq_len;
17007             clen = cseq_len;
17008             continue;
17009         }
17010
17011         if (dest)
17012             dest[len] = src[i];
17013         len++;
17014         if (!ansi)
17015             line++;
17016         if (src[i] == '\n')
17017             line = 0;
17018         if (src[i] == 'm')
17019             ansi = 0;
17020     }
17021     if (dest && appData.debugMode)
17022     {
17023         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17024             count, width, line, len, *lp);
17025         show_bytes(debugFP, src, count);
17026         fprintf(debugFP, "\ndest: ");
17027         show_bytes(debugFP, dest, len);
17028         fprintf(debugFP, "\n");
17029     }
17030     *lp = dest ? line : old_line;
17031
17032     return len;
17033 }
17034
17035 // [HGM] vari: routines for shelving variations
17036 Boolean modeRestore = FALSE;
17037
17038 void
17039 PushInner (int firstMove, int lastMove)
17040 {
17041         int i, j, nrMoves = lastMove - firstMove;
17042
17043         // push current tail of game on stack
17044         savedResult[storedGames] = gameInfo.result;
17045         savedDetails[storedGames] = gameInfo.resultDetails;
17046         gameInfo.resultDetails = NULL;
17047         savedFirst[storedGames] = firstMove;
17048         savedLast [storedGames] = lastMove;
17049         savedFramePtr[storedGames] = framePtr;
17050         framePtr -= nrMoves; // reserve space for the boards
17051         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17052             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17053             for(j=0; j<MOVE_LEN; j++)
17054                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17055             for(j=0; j<2*MOVE_LEN; j++)
17056                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17057             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17058             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17059             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17060             pvInfoList[firstMove+i-1].depth = 0;
17061             commentList[framePtr+i] = commentList[firstMove+i];
17062             commentList[firstMove+i] = NULL;
17063         }
17064
17065         storedGames++;
17066         forwardMostMove = firstMove; // truncate game so we can start variation
17067 }
17068
17069 void
17070 PushTail (int firstMove, int lastMove)
17071 {
17072         if(appData.icsActive) { // only in local mode
17073                 forwardMostMove = currentMove; // mimic old ICS behavior
17074                 return;
17075         }
17076         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17077
17078         PushInner(firstMove, lastMove);
17079         if(storedGames == 1) GreyRevert(FALSE);
17080         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17081 }
17082
17083 void
17084 PopInner (Boolean annotate)
17085 {
17086         int i, j, nrMoves;
17087         char buf[8000], moveBuf[20];
17088
17089         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17090         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17091         nrMoves = savedLast[storedGames] - currentMove;
17092         if(annotate) {
17093                 int cnt = 10;
17094                 if(!WhiteOnMove(currentMove))
17095                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17096                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17097                 for(i=currentMove; i<forwardMostMove; i++) {
17098                         if(WhiteOnMove(i))
17099                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17100                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17101                         strcat(buf, moveBuf);
17102                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17103                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17104                 }
17105                 strcat(buf, ")");
17106         }
17107         for(i=1; i<=nrMoves; i++) { // copy last variation back
17108             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17109             for(j=0; j<MOVE_LEN; j++)
17110                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17111             for(j=0; j<2*MOVE_LEN; j++)
17112                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17113             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17114             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17115             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17116             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17117             commentList[currentMove+i] = commentList[framePtr+i];
17118             commentList[framePtr+i] = NULL;
17119         }
17120         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17121         framePtr = savedFramePtr[storedGames];
17122         gameInfo.result = savedResult[storedGames];
17123         if(gameInfo.resultDetails != NULL) {
17124             free(gameInfo.resultDetails);
17125       }
17126         gameInfo.resultDetails = savedDetails[storedGames];
17127         forwardMostMove = currentMove + nrMoves;
17128 }
17129
17130 Boolean
17131 PopTail (Boolean annotate)
17132 {
17133         if(appData.icsActive) return FALSE; // only in local mode
17134         if(!storedGames) return FALSE; // sanity
17135         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17136
17137         PopInner(annotate);
17138         if(currentMove < forwardMostMove) ForwardEvent(); else
17139         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17140
17141         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17142         return TRUE;
17143 }
17144
17145 void
17146 CleanupTail ()
17147 {       // remove all shelved variations
17148         int i;
17149         for(i=0; i<storedGames; i++) {
17150             if(savedDetails[i])
17151                 free(savedDetails[i]);
17152             savedDetails[i] = NULL;
17153         }
17154         for(i=framePtr; i<MAX_MOVES; i++) {
17155                 if(commentList[i]) free(commentList[i]);
17156                 commentList[i] = NULL;
17157         }
17158         framePtr = MAX_MOVES-1;
17159         storedGames = 0;
17160 }
17161
17162 void
17163 LoadVariation (int index, char *text)
17164 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17165         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17166         int level = 0, move;
17167
17168         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17169         // first find outermost bracketing variation
17170         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17171             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17172                 if(*p == '{') wait = '}'; else
17173                 if(*p == '[') wait = ']'; else
17174                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17175                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17176             }
17177             if(*p == wait) wait = NULLCHAR; // closing ]} found
17178             p++;
17179         }
17180         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17181         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17182         end[1] = NULLCHAR; // clip off comment beyond variation
17183         ToNrEvent(currentMove-1);
17184         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17185         // kludge: use ParsePV() to append variation to game
17186         move = currentMove;
17187         ParsePV(start, TRUE, TRUE);
17188         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17189         ClearPremoveHighlights();
17190         CommentPopDown();
17191         ToNrEvent(currentMove+1);
17192 }
17193