38c3533e2558261492dca1d1bea98980e46b62e4
[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 < currentMove; 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) {
4269           DisplayWhiteClock(white_time, to_play == 'W');
4270           DisplayBlackClock(black_time, to_play != 'W');
4271                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4272       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4273                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4274       DisplayMessage(partnerStatus, "");
4275         partnerBoardValid = TRUE;
4276       return;
4277     }
4278
4279     /* Modify behavior for initial board display on move listing
4280        of wild games.
4281        */
4282     switch (ics_getting_history) {
4283       case H_FALSE:
4284       case H_REQUESTED:
4285         break;
4286       case H_GOT_REQ_HEADER:
4287       case H_GOT_UNREQ_HEADER:
4288         /* This is the initial position of the current game */
4289         gamenum = ics_gamenum;
4290         moveNum = 0;            /* old ICS bug workaround */
4291         if (to_play == 'B') {
4292           startedFromSetupPosition = TRUE;
4293           blackPlaysFirst = TRUE;
4294           moveNum = 1;
4295           if (forwardMostMove == 0) forwardMostMove = 1;
4296           if (backwardMostMove == 0) backwardMostMove = 1;
4297           if (currentMove == 0) currentMove = 1;
4298         }
4299         newGameMode = gameMode;
4300         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4301         break;
4302       case H_GOT_UNWANTED_HEADER:
4303         /* This is an initial board that we don't want */
4304         return;
4305       case H_GETTING_MOVES:
4306         /* Should not happen */
4307         DisplayError(_("Error gathering move list: extra board"), 0);
4308         ics_getting_history = H_FALSE;
4309         return;
4310     }
4311
4312    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4313                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4314      /* [HGM] We seem to have switched variant unexpectedly
4315       * Try to guess new variant from board size
4316       */
4317           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4318           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4319           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4320           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4321           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4322           if(!weird) newVariant = VariantNormal;
4323           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4324           /* Get a move list just to see the header, which
4325              will tell us whether this is really bug or zh */
4326           if (ics_getting_history == H_FALSE) {
4327             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4328             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4329             SendToICS(str);
4330           }
4331     }
4332
4333     /* Take action if this is the first board of a new game, or of a
4334        different game than is currently being displayed.  */
4335     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4336         relation == RELATION_ISOLATED_BOARD) {
4337
4338         /* Forget the old game and get the history (if any) of the new one */
4339         if (gameMode != BeginningOfGame) {
4340           Reset(TRUE, TRUE);
4341         }
4342         newGame = TRUE;
4343         if (appData.autoRaiseBoard) BoardToTop();
4344         prevMove = -3;
4345         if (gamenum == -1) {
4346             newGameMode = IcsIdle;
4347         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4348                    appData.getMoveList && !reqFlag) {
4349             /* Need to get game history */
4350             ics_getting_history = H_REQUESTED;
4351             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4352             SendToICS(str);
4353         }
4354
4355         /* Initially flip the board to have black on the bottom if playing
4356            black or if the ICS flip flag is set, but let the user change
4357            it with the Flip View button. */
4358         flipView = appData.autoFlipView ?
4359           (newGameMode == IcsPlayingBlack) || ics_flip :
4360           appData.flipView;
4361
4362         /* Done with values from previous mode; copy in new ones */
4363         gameMode = newGameMode;
4364         ModeHighlight();
4365         ics_gamenum = gamenum;
4366         if (gamenum == gs_gamenum) {
4367             int klen = strlen(gs_kind);
4368             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4369             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4370             gameInfo.event = StrSave(str);
4371         } else {
4372             gameInfo.event = StrSave("ICS game");
4373         }
4374         gameInfo.site = StrSave(appData.icsHost);
4375         gameInfo.date = PGNDate();
4376         gameInfo.round = StrSave("-");
4377         gameInfo.white = StrSave(white);
4378         gameInfo.black = StrSave(black);
4379         timeControl = basetime * 60 * 1000;
4380         timeControl_2 = 0;
4381         timeIncrement = increment * 1000;
4382         movesPerSession = 0;
4383         gameInfo.timeControl = TimeControlTagValue();
4384         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4385   if (appData.debugMode) {
4386     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4387     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4388     setbuf(debugFP, NULL);
4389   }
4390
4391         gameInfo.outOfBook = NULL;
4392
4393         /* Do we have the ratings? */
4394         if (strcmp(player1Name, white) == 0 &&
4395             strcmp(player2Name, black) == 0) {
4396             if (appData.debugMode)
4397               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4398                       player1Rating, player2Rating);
4399             gameInfo.whiteRating = player1Rating;
4400             gameInfo.blackRating = player2Rating;
4401         } else if (strcmp(player2Name, white) == 0 &&
4402                    strcmp(player1Name, black) == 0) {
4403             if (appData.debugMode)
4404               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4405                       player2Rating, player1Rating);
4406             gameInfo.whiteRating = player2Rating;
4407             gameInfo.blackRating = player1Rating;
4408         }
4409         player1Name[0] = player2Name[0] = NULLCHAR;
4410
4411         /* Silence shouts if requested */
4412         if (appData.quietPlay &&
4413             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4414             SendToICS(ics_prefix);
4415             SendToICS("set shout 0\n");
4416         }
4417     }
4418
4419     /* Deal with midgame name changes */
4420     if (!newGame) {
4421         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4422             if (gameInfo.white) free(gameInfo.white);
4423             gameInfo.white = StrSave(white);
4424         }
4425         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4426             if (gameInfo.black) free(gameInfo.black);
4427             gameInfo.black = StrSave(black);
4428         }
4429     }
4430
4431     /* Throw away game result if anything actually changes in examine mode */
4432     if (gameMode == IcsExamining && !newGame) {
4433         gameInfo.result = GameUnfinished;
4434         if (gameInfo.resultDetails != NULL) {
4435             free(gameInfo.resultDetails);
4436             gameInfo.resultDetails = NULL;
4437         }
4438     }
4439
4440     /* In pausing && IcsExamining mode, we ignore boards coming
4441        in if they are in a different variation than we are. */
4442     if (pauseExamInvalid) return;
4443     if (pausing && gameMode == IcsExamining) {
4444         if (moveNum <= pauseExamForwardMostMove) {
4445             pauseExamInvalid = TRUE;
4446             forwardMostMove = pauseExamForwardMostMove;
4447             return;
4448         }
4449     }
4450
4451   if (appData.debugMode) {
4452     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4453   }
4454     /* Parse the board */
4455     for (k = 0; k < ranks; k++) {
4456       for (j = 0; j < files; j++)
4457         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4458       if(gameInfo.holdingsWidth > 1) {
4459            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4460            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4461       }
4462     }
4463     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4464       board[5][BOARD_RGHT+1] = WhiteAngel;
4465       board[6][BOARD_RGHT+1] = WhiteMarshall;
4466       board[1][0] = BlackMarshall;
4467       board[2][0] = BlackAngel;
4468       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4469     }
4470     CopyBoard(boards[moveNum], board);
4471     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4472     if (moveNum == 0) {
4473         startedFromSetupPosition =
4474           !CompareBoards(board, initialPosition);
4475         if(startedFromSetupPosition)
4476             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4477     }
4478
4479     /* [HGM] Set castling rights. Take the outermost Rooks,
4480        to make it also work for FRC opening positions. Note that board12
4481        is really defective for later FRC positions, as it has no way to
4482        indicate which Rook can castle if they are on the same side of King.
4483        For the initial position we grant rights to the outermost Rooks,
4484        and remember thos rights, and we then copy them on positions
4485        later in an FRC game. This means WB might not recognize castlings with
4486        Rooks that have moved back to their original position as illegal,
4487        but in ICS mode that is not its job anyway.
4488     */
4489     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4490     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4491
4492         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4493             if(board[0][i] == WhiteRook) j = i;
4494         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4495         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4496             if(board[0][i] == WhiteRook) j = i;
4497         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4498         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4499             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4500         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4501         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4502             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4503         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4504
4505         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4506         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4507         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4508             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4509         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4510             if(board[BOARD_HEIGHT-1][k] == bKing)
4511                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4512         if(gameInfo.variant == VariantTwoKings) {
4513             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4514             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4515             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4516         }
4517     } else { int r;
4518         r = boards[moveNum][CASTLING][0] = initialRights[0];
4519         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4520         r = boards[moveNum][CASTLING][1] = initialRights[1];
4521         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4522         r = boards[moveNum][CASTLING][3] = initialRights[3];
4523         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4524         r = boards[moveNum][CASTLING][4] = initialRights[4];
4525         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4526         /* wildcastle kludge: always assume King has rights */
4527         r = boards[moveNum][CASTLING][2] = initialRights[2];
4528         r = boards[moveNum][CASTLING][5] = initialRights[5];
4529     }
4530     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4531     boards[moveNum][EP_STATUS] = EP_NONE;
4532     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4533     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4534     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4535
4536
4537     if (ics_getting_history == H_GOT_REQ_HEADER ||
4538         ics_getting_history == H_GOT_UNREQ_HEADER) {
4539         /* This was an initial position from a move list, not
4540            the current position */
4541         return;
4542     }
4543
4544     /* Update currentMove and known move number limits */
4545     newMove = newGame || moveNum > forwardMostMove;
4546
4547     if (newGame) {
4548         forwardMostMove = backwardMostMove = currentMove = moveNum;
4549         if (gameMode == IcsExamining && moveNum == 0) {
4550           /* Workaround for ICS limitation: we are not told the wild
4551              type when starting to examine a game.  But if we ask for
4552              the move list, the move list header will tell us */
4553             ics_getting_history = H_REQUESTED;
4554             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4555             SendToICS(str);
4556         }
4557     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4558                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4559 #if ZIPPY
4560         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4561         /* [HGM] applied this also to an engine that is silently watching        */
4562         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4563             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4564             gameInfo.variant == currentlyInitializedVariant) {
4565           takeback = forwardMostMove - moveNum;
4566           for (i = 0; i < takeback; i++) {
4567             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4568             SendToProgram("undo\n", &first);
4569           }
4570         }
4571 #endif
4572
4573         forwardMostMove = moveNum;
4574         if (!pausing || currentMove > forwardMostMove)
4575           currentMove = forwardMostMove;
4576     } else {
4577         /* New part of history that is not contiguous with old part */
4578         if (pausing && gameMode == IcsExamining) {
4579             pauseExamInvalid = TRUE;
4580             forwardMostMove = pauseExamForwardMostMove;
4581             return;
4582         }
4583         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4584 #if ZIPPY
4585             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4586                 // [HGM] when we will receive the move list we now request, it will be
4587                 // fed to the engine from the first move on. So if the engine is not
4588                 // in the initial position now, bring it there.
4589                 InitChessProgram(&first, 0);
4590             }
4591 #endif
4592             ics_getting_history = H_REQUESTED;
4593             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4594             SendToICS(str);
4595         }
4596         forwardMostMove = backwardMostMove = currentMove = moveNum;
4597     }
4598
4599     /* Update the clocks */
4600     if (strchr(elapsed_time, '.')) {
4601       /* Time is in ms */
4602       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4603       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4604     } else {
4605       /* Time is in seconds */
4606       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4607       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4608     }
4609
4610
4611 #if ZIPPY
4612     if (appData.zippyPlay && newGame &&
4613         gameMode != IcsObserving && gameMode != IcsIdle &&
4614         gameMode != IcsExamining)
4615       ZippyFirstBoard(moveNum, basetime, increment);
4616 #endif
4617
4618     /* Put the move on the move list, first converting
4619        to canonical algebraic form. */
4620     if (moveNum > 0) {
4621   if (appData.debugMode) {
4622     if (appData.debugMode) { int f = forwardMostMove;
4623         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4624                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4625                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4626     }
4627     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4628     fprintf(debugFP, "moveNum = %d\n", moveNum);
4629     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4630     setbuf(debugFP, NULL);
4631   }
4632         if (moveNum <= backwardMostMove) {
4633             /* We don't know what the board looked like before
4634                this move.  Punt. */
4635           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4636             strcat(parseList[moveNum - 1], " ");
4637             strcat(parseList[moveNum - 1], elapsed_time);
4638             moveList[moveNum - 1][0] = NULLCHAR;
4639         } else if (strcmp(move_str, "none") == 0) {
4640             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4641             /* Again, we don't know what the board looked like;
4642                this is really the start of the game. */
4643             parseList[moveNum - 1][0] = NULLCHAR;
4644             moveList[moveNum - 1][0] = NULLCHAR;
4645             backwardMostMove = moveNum;
4646             startedFromSetupPosition = TRUE;
4647             fromX = fromY = toX = toY = -1;
4648         } else {
4649           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4650           //                 So we parse the long-algebraic move string in stead of the SAN move
4651           int valid; char buf[MSG_SIZ], *prom;
4652
4653           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4654                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4655           // str looks something like "Q/a1-a2"; kill the slash
4656           if(str[1] == '/')
4657             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4658           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4659           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4660                 strcat(buf, prom); // long move lacks promo specification!
4661           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4662                 if(appData.debugMode)
4663                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4664                 safeStrCpy(move_str, buf, MSG_SIZ);
4665           }
4666           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4667                                 &fromX, &fromY, &toX, &toY, &promoChar)
4668                || ParseOneMove(buf, moveNum - 1, &moveType,
4669                                 &fromX, &fromY, &toX, &toY, &promoChar);
4670           // end of long SAN patch
4671           if (valid) {
4672             (void) CoordsToAlgebraic(boards[moveNum - 1],
4673                                      PosFlags(moveNum - 1),
4674                                      fromY, fromX, toY, toX, promoChar,
4675                                      parseList[moveNum-1]);
4676             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4677               case MT_NONE:
4678               case MT_STALEMATE:
4679               default:
4680                 break;
4681               case MT_CHECK:
4682                 if(gameInfo.variant != VariantShogi)
4683                     strcat(parseList[moveNum - 1], "+");
4684                 break;
4685               case MT_CHECKMATE:
4686               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4687                 strcat(parseList[moveNum - 1], "#");
4688                 break;
4689             }
4690             strcat(parseList[moveNum - 1], " ");
4691             strcat(parseList[moveNum - 1], elapsed_time);
4692             /* currentMoveString is set as a side-effect of ParseOneMove */
4693             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4694             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4695             strcat(moveList[moveNum - 1], "\n");
4696
4697             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4698                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4699               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4700                 ChessSquare old, new = boards[moveNum][k][j];
4701                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4702                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4703                   if(old == new) continue;
4704                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4705                   else if(new == WhiteWazir || new == BlackWazir) {
4706                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4707                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4708                       else boards[moveNum][k][j] = old; // preserve type of Gold
4709                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4710                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4711               }
4712           } else {
4713             /* Move from ICS was illegal!?  Punt. */
4714             if (appData.debugMode) {
4715               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4716               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4717             }
4718             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4719             strcat(parseList[moveNum - 1], " ");
4720             strcat(parseList[moveNum - 1], elapsed_time);
4721             moveList[moveNum - 1][0] = NULLCHAR;
4722             fromX = fromY = toX = toY = -1;
4723           }
4724         }
4725   if (appData.debugMode) {
4726     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4727     setbuf(debugFP, NULL);
4728   }
4729
4730 #if ZIPPY
4731         /* Send move to chess program (BEFORE animating it). */
4732         if (appData.zippyPlay && !newGame && newMove &&
4733            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4734
4735             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4736                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4737                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4738                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4739                             move_str);
4740                     DisplayError(str, 0);
4741                 } else {
4742                     if (first.sendTime) {
4743                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4744                     }
4745                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4746                     if (firstMove && !bookHit) {
4747                         firstMove = FALSE;
4748                         if (first.useColors) {
4749                           SendToProgram(gameMode == IcsPlayingWhite ?
4750                                         "white\ngo\n" :
4751                                         "black\ngo\n", &first);
4752                         } else {
4753                           SendToProgram("go\n", &first);
4754                         }
4755                         first.maybeThinking = TRUE;
4756                     }
4757                 }
4758             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4759               if (moveList[moveNum - 1][0] == NULLCHAR) {
4760                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4761                 DisplayError(str, 0);
4762               } else {
4763                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4764                 SendMoveToProgram(moveNum - 1, &first);
4765               }
4766             }
4767         }
4768 #endif
4769     }
4770
4771     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4772         /* If move comes from a remote source, animate it.  If it
4773            isn't remote, it will have already been animated. */
4774         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4775             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4776         }
4777         if (!pausing && appData.highlightLastMove) {
4778             SetHighlights(fromX, fromY, toX, toY);
4779         }
4780     }
4781
4782     /* Start the clocks */
4783     whiteFlag = blackFlag = FALSE;
4784     appData.clockMode = !(basetime == 0 && increment == 0);
4785     if (ticking == 0) {
4786       ics_clock_paused = TRUE;
4787       StopClocks();
4788     } else if (ticking == 1) {
4789       ics_clock_paused = FALSE;
4790     }
4791     if (gameMode == IcsIdle ||
4792         relation == RELATION_OBSERVING_STATIC ||
4793         relation == RELATION_EXAMINING ||
4794         ics_clock_paused)
4795       DisplayBothClocks();
4796     else
4797       StartClocks();
4798
4799     /* Display opponents and material strengths */
4800     if (gameInfo.variant != VariantBughouse &&
4801         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4802         if (tinyLayout || smallLayout) {
4803             if(gameInfo.variant == VariantNormal)
4804               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4805                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4806                     basetime, increment);
4807             else
4808               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4809                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4810                     basetime, increment, (int) gameInfo.variant);
4811         } else {
4812             if(gameInfo.variant == VariantNormal)
4813               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4814                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4815                     basetime, increment);
4816             else
4817               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4818                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4819                     basetime, increment, VariantName(gameInfo.variant));
4820         }
4821         DisplayTitle(str);
4822   if (appData.debugMode) {
4823     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4824   }
4825     }
4826
4827
4828     /* Display the board */
4829     if (!pausing && !appData.noGUI) {
4830
4831       if (appData.premove)
4832           if (!gotPremove ||
4833              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4834              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4835               ClearPremoveHighlights();
4836
4837       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4838         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4839       DrawPosition(j, boards[currentMove]);
4840
4841       DisplayMove(moveNum - 1);
4842       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4843             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4844               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4845         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4846       }
4847     }
4848
4849     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4850 #if ZIPPY
4851     if(bookHit) { // [HGM] book: simulate book reply
4852         static char bookMove[MSG_SIZ]; // a bit generous?
4853
4854         programStats.nodes = programStats.depth = programStats.time =
4855         programStats.score = programStats.got_only_move = 0;
4856         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4857
4858         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4859         strcat(bookMove, bookHit);
4860         HandleMachineMove(bookMove, &first);
4861     }
4862 #endif
4863 }
4864
4865 void
4866 GetMoveListEvent ()
4867 {
4868     char buf[MSG_SIZ];
4869     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4870         ics_getting_history = H_REQUESTED;
4871         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4872         SendToICS(buf);
4873     }
4874 }
4875
4876 void
4877 AnalysisPeriodicEvent (int force)
4878 {
4879     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4880          && !force) || !appData.periodicUpdates)
4881       return;
4882
4883     /* Send . command to Crafty to collect stats */
4884     SendToProgram(".\n", &first);
4885
4886     /* Don't send another until we get a response (this makes
4887        us stop sending to old Crafty's which don't understand
4888        the "." command (sending illegal cmds resets node count & time,
4889        which looks bad)) */
4890     programStats.ok_to_send = 0;
4891 }
4892
4893 void
4894 ics_update_width (int new_width)
4895 {
4896         ics_printf("set width %d\n", new_width);
4897 }
4898
4899 void
4900 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4901 {
4902     char buf[MSG_SIZ];
4903
4904     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4905         // null move in variant where engine does not understand it (for analysis purposes)
4906         SendBoard(cps, moveNum + 1); // send position after move in stead.
4907         return;
4908     }
4909     if (cps->useUsermove) {
4910       SendToProgram("usermove ", cps);
4911     }
4912     if (cps->useSAN) {
4913       char *space;
4914       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4915         int len = space - parseList[moveNum];
4916         memcpy(buf, parseList[moveNum], len);
4917         buf[len++] = '\n';
4918         buf[len] = NULLCHAR;
4919       } else {
4920         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4921       }
4922       SendToProgram(buf, cps);
4923     } else {
4924       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4925         AlphaRank(moveList[moveNum], 4);
4926         SendToProgram(moveList[moveNum], cps);
4927         AlphaRank(moveList[moveNum], 4); // and back
4928       } else
4929       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4930        * the engine. It would be nice to have a better way to identify castle
4931        * moves here. */
4932       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4933                                                                          && cps->useOOCastle) {
4934         int fromX = moveList[moveNum][0] - AAA;
4935         int fromY = moveList[moveNum][1] - ONE;
4936         int toX = moveList[moveNum][2] - AAA;
4937         int toY = moveList[moveNum][3] - ONE;
4938         if((boards[moveNum][fromY][fromX] == WhiteKing
4939             && boards[moveNum][toY][toX] == WhiteRook)
4940            || (boards[moveNum][fromY][fromX] == BlackKing
4941                && boards[moveNum][toY][toX] == BlackRook)) {
4942           if(toX > fromX) SendToProgram("O-O\n", cps);
4943           else SendToProgram("O-O-O\n", cps);
4944         }
4945         else SendToProgram(moveList[moveNum], cps);
4946       } else
4947       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4948         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4949           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4950           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4951                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4952         } else
4953           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4954                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4955         SendToProgram(buf, cps);
4956       }
4957       else SendToProgram(moveList[moveNum], cps);
4958       /* End of additions by Tord */
4959     }
4960
4961     /* [HGM] setting up the opening has brought engine in force mode! */
4962     /*       Send 'go' if we are in a mode where machine should play. */
4963     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4964         (gameMode == TwoMachinesPlay   ||
4965 #if ZIPPY
4966          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4967 #endif
4968          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4969         SendToProgram("go\n", cps);
4970   if (appData.debugMode) {
4971     fprintf(debugFP, "(extra)\n");
4972   }
4973     }
4974     setboardSpoiledMachineBlack = 0;
4975 }
4976
4977 void
4978 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4979 {
4980     char user_move[MSG_SIZ];
4981     char suffix[4];
4982
4983     if(gameInfo.variant == VariantSChess && promoChar) {
4984         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4985         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4986     } else suffix[0] = NULLCHAR;
4987
4988     switch (moveType) {
4989       default:
4990         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4991                 (int)moveType, fromX, fromY, toX, toY);
4992         DisplayError(user_move + strlen("say "), 0);
4993         break;
4994       case WhiteKingSideCastle:
4995       case BlackKingSideCastle:
4996       case WhiteQueenSideCastleWild:
4997       case BlackQueenSideCastleWild:
4998       /* PUSH Fabien */
4999       case WhiteHSideCastleFR:
5000       case BlackHSideCastleFR:
5001       /* POP Fabien */
5002         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5003         break;
5004       case WhiteQueenSideCastle:
5005       case BlackQueenSideCastle:
5006       case WhiteKingSideCastleWild:
5007       case BlackKingSideCastleWild:
5008       /* PUSH Fabien */
5009       case WhiteASideCastleFR:
5010       case BlackASideCastleFR:
5011       /* POP Fabien */
5012         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5013         break;
5014       case WhiteNonPromotion:
5015       case BlackNonPromotion:
5016         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5017         break;
5018       case WhitePromotion:
5019       case BlackPromotion:
5020         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5021           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5022                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5023                 PieceToChar(WhiteFerz));
5024         else if(gameInfo.variant == VariantGreat)
5025           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5026                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5027                 PieceToChar(WhiteMan));
5028         else
5029           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5030                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5031                 promoChar);
5032         break;
5033       case WhiteDrop:
5034       case BlackDrop:
5035       drop:
5036         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5037                  ToUpper(PieceToChar((ChessSquare) fromX)),
5038                  AAA + toX, ONE + toY);
5039         break;
5040       case IllegalMove:  /* could be a variant we don't quite understand */
5041         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5042       case NormalMove:
5043       case WhiteCapturesEnPassant:
5044       case BlackCapturesEnPassant:
5045         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5046                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5047         break;
5048     }
5049     SendToICS(user_move);
5050     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5051         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5052 }
5053
5054 void
5055 UploadGameEvent ()
5056 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5057     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5058     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5059     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5060       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5061       return;
5062     }
5063     if(gameMode != IcsExamining) { // is this ever not the case?
5064         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5065
5066         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5067           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5068         } else { // on FICS we must first go to general examine mode
5069           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5070         }
5071         if(gameInfo.variant != VariantNormal) {
5072             // try figure out wild number, as xboard names are not always valid on ICS
5073             for(i=1; i<=36; i++) {
5074               snprintf(buf, MSG_SIZ, "wild/%d", i);
5075                 if(StringToVariant(buf) == gameInfo.variant) break;
5076             }
5077             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5078             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5079             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5080         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5081         SendToICS(ics_prefix);
5082         SendToICS(buf);
5083         if(startedFromSetupPosition || backwardMostMove != 0) {
5084           fen = PositionToFEN(backwardMostMove, NULL);
5085           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5086             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5087             SendToICS(buf);
5088           } else { // FICS: everything has to set by separate bsetup commands
5089             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5090             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5091             SendToICS(buf);
5092             if(!WhiteOnMove(backwardMostMove)) {
5093                 SendToICS("bsetup tomove black\n");
5094             }
5095             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5096             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5097             SendToICS(buf);
5098             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5099             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5100             SendToICS(buf);
5101             i = boards[backwardMostMove][EP_STATUS];
5102             if(i >= 0) { // set e.p.
5103               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5104                 SendToICS(buf);
5105             }
5106             bsetup++;
5107           }
5108         }
5109       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5110             SendToICS("bsetup done\n"); // switch to normal examining.
5111     }
5112     for(i = backwardMostMove; i<last; i++) {
5113         char buf[20];
5114         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5115         SendToICS(buf);
5116     }
5117     SendToICS(ics_prefix);
5118     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5119 }
5120
5121 void
5122 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5123 {
5124     if (rf == DROP_RANK) {
5125       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5126       sprintf(move, "%c@%c%c\n",
5127                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5128     } else {
5129         if (promoChar == 'x' || promoChar == NULLCHAR) {
5130           sprintf(move, "%c%c%c%c\n",
5131                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5132         } else {
5133             sprintf(move, "%c%c%c%c%c\n",
5134                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5135         }
5136     }
5137 }
5138
5139 void
5140 ProcessICSInitScript (FILE *f)
5141 {
5142     char buf[MSG_SIZ];
5143
5144     while (fgets(buf, MSG_SIZ, f)) {
5145         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5146     }
5147
5148     fclose(f);
5149 }
5150
5151
5152 static int lastX, lastY, selectFlag, dragging;
5153
5154 void
5155 Sweep (int step)
5156 {
5157     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5158     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5159     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5160     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5161     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5162     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5163     do {
5164         promoSweep -= step;
5165         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5166         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5167         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5168         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5169         if(!step) step = -1;
5170     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5171             appData.testLegality && (promoSweep == king ||
5172             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5173     ChangeDragPiece(promoSweep);
5174 }
5175
5176 int
5177 PromoScroll (int x, int y)
5178 {
5179   int step = 0;
5180
5181   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5182   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5183   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5184   if(!step) return FALSE;
5185   lastX = x; lastY = y;
5186   if((promoSweep < BlackPawn) == flipView) step = -step;
5187   if(step > 0) selectFlag = 1;
5188   if(!selectFlag) Sweep(step);
5189   return FALSE;
5190 }
5191
5192 void
5193 NextPiece (int step)
5194 {
5195     ChessSquare piece = boards[currentMove][toY][toX];
5196     do {
5197         pieceSweep -= step;
5198         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5199         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5200         if(!step) step = -1;
5201     } while(PieceToChar(pieceSweep) == '.');
5202     boards[currentMove][toY][toX] = pieceSweep;
5203     DrawPosition(FALSE, boards[currentMove]);
5204     boards[currentMove][toY][toX] = piece;
5205 }
5206 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5207 void
5208 AlphaRank (char *move, int n)
5209 {
5210 //    char *p = move, c; int x, y;
5211
5212     if (appData.debugMode) {
5213         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5214     }
5215
5216     if(move[1]=='*' &&
5217        move[2]>='0' && move[2]<='9' &&
5218        move[3]>='a' && move[3]<='x'    ) {
5219         move[1] = '@';
5220         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5221         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5222     } else
5223     if(move[0]>='0' && move[0]<='9' &&
5224        move[1]>='a' && move[1]<='x' &&
5225        move[2]>='0' && move[2]<='9' &&
5226        move[3]>='a' && move[3]<='x'    ) {
5227         /* input move, Shogi -> normal */
5228         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5229         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5230         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5231         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5232     } else
5233     if(move[1]=='@' &&
5234        move[3]>='0' && move[3]<='9' &&
5235        move[2]>='a' && move[2]<='x'    ) {
5236         move[1] = '*';
5237         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5238         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5239     } else
5240     if(
5241        move[0]>='a' && move[0]<='x' &&
5242        move[3]>='0' && move[3]<='9' &&
5243        move[2]>='a' && move[2]<='x'    ) {
5244          /* output move, normal -> Shogi */
5245         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5246         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5247         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5248         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5249         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5250     }
5251     if (appData.debugMode) {
5252         fprintf(debugFP, "   out = '%s'\n", move);
5253     }
5254 }
5255
5256 char yy_textstr[8000];
5257
5258 /* Parser for moves from gnuchess, ICS, or user typein box */
5259 Boolean
5260 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5261 {
5262     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5263
5264     switch (*moveType) {
5265       case WhitePromotion:
5266       case BlackPromotion:
5267       case WhiteNonPromotion:
5268       case BlackNonPromotion:
5269       case NormalMove:
5270       case WhiteCapturesEnPassant:
5271       case BlackCapturesEnPassant:
5272       case WhiteKingSideCastle:
5273       case WhiteQueenSideCastle:
5274       case BlackKingSideCastle:
5275       case BlackQueenSideCastle:
5276       case WhiteKingSideCastleWild:
5277       case WhiteQueenSideCastleWild:
5278       case BlackKingSideCastleWild:
5279       case BlackQueenSideCastleWild:
5280       /* Code added by Tord: */
5281       case WhiteHSideCastleFR:
5282       case WhiteASideCastleFR:
5283       case BlackHSideCastleFR:
5284       case BlackASideCastleFR:
5285       /* End of code added by Tord */
5286       case IllegalMove:         /* bug or odd chess variant */
5287         *fromX = currentMoveString[0] - AAA;
5288         *fromY = currentMoveString[1] - ONE;
5289         *toX = currentMoveString[2] - AAA;
5290         *toY = currentMoveString[3] - ONE;
5291         *promoChar = currentMoveString[4];
5292         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5293             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5294     if (appData.debugMode) {
5295         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5296     }
5297             *fromX = *fromY = *toX = *toY = 0;
5298             return FALSE;
5299         }
5300         if (appData.testLegality) {
5301           return (*moveType != IllegalMove);
5302         } else {
5303           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5304                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5305         }
5306
5307       case WhiteDrop:
5308       case BlackDrop:
5309         *fromX = *moveType == WhiteDrop ?
5310           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5311           (int) CharToPiece(ToLower(currentMoveString[0]));
5312         *fromY = DROP_RANK;
5313         *toX = currentMoveString[2] - AAA;
5314         *toY = currentMoveString[3] - ONE;
5315         *promoChar = NULLCHAR;
5316         return TRUE;
5317
5318       case AmbiguousMove:
5319       case ImpossibleMove:
5320       case EndOfFile:
5321       case ElapsedTime:
5322       case Comment:
5323       case PGNTag:
5324       case NAG:
5325       case WhiteWins:
5326       case BlackWins:
5327       case GameIsDrawn:
5328       default:
5329     if (appData.debugMode) {
5330         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5331     }
5332         /* bug? */
5333         *fromX = *fromY = *toX = *toY = 0;
5334         *promoChar = NULLCHAR;
5335         return FALSE;
5336     }
5337 }
5338
5339 Boolean pushed = FALSE;
5340 char *lastParseAttempt;
5341
5342 void
5343 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5344 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5345   int fromX, fromY, toX, toY; char promoChar;
5346   ChessMove moveType;
5347   Boolean valid;
5348   int nr = 0;
5349
5350   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5351     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5352     pushed = TRUE;
5353   }
5354   endPV = forwardMostMove;
5355   do {
5356     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5357     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5358     lastParseAttempt = pv;
5359     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5360     if(!valid && nr == 0 &&
5361        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5362         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5363         // Hande case where played move is different from leading PV move
5364         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5365         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5366         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5367         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5368           endPV += 2; // if position different, keep this
5369           moveList[endPV-1][0] = fromX + AAA;
5370           moveList[endPV-1][1] = fromY + ONE;
5371           moveList[endPV-1][2] = toX + AAA;
5372           moveList[endPV-1][3] = toY + ONE;
5373           parseList[endPV-1][0] = NULLCHAR;
5374           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5375         }
5376       }
5377     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5378     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5379     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5380     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5381         valid++; // allow comments in PV
5382         continue;
5383     }
5384     nr++;
5385     if(endPV+1 > framePtr) break; // no space, truncate
5386     if(!valid) break;
5387     endPV++;
5388     CopyBoard(boards[endPV], boards[endPV-1]);
5389     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5390     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5391     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5392     CoordsToAlgebraic(boards[endPV - 1],
5393                              PosFlags(endPV - 1),
5394                              fromY, fromX, toY, toX, promoChar,
5395                              parseList[endPV - 1]);
5396   } while(valid);
5397   if(atEnd == 2) return; // used hidden, for PV conversion
5398   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5399   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5400   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5401                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5402   DrawPosition(TRUE, boards[currentMove]);
5403 }
5404
5405 int
5406 MultiPV (ChessProgramState *cps)
5407 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5408         int i;
5409         for(i=0; i<cps->nrOptions; i++)
5410             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5411                 return i;
5412         return -1;
5413 }
5414
5415 Boolean
5416 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5417 {
5418         int startPV, multi, lineStart, origIndex = index;
5419         char *p, buf2[MSG_SIZ];
5420
5421         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5422         lastX = x; lastY = y;
5423         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5424         lineStart = startPV = index;
5425         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5426         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5427         index = startPV;
5428         do{ while(buf[index] && buf[index] != '\n') index++;
5429         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5430         buf[index] = 0;
5431         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5432                 int n = first.option[multi].value;
5433                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5434                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5435                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5436                 first.option[multi].value = n;
5437                 *start = *end = 0;
5438                 return FALSE;
5439         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5440                 ExcludeClick(origIndex - lineStart);
5441                 return FALSE;
5442         }
5443         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5444         *start = startPV; *end = index-1;
5445         return TRUE;
5446 }
5447
5448 char *
5449 PvToSAN (char *pv)
5450 {
5451         static char buf[10*MSG_SIZ];
5452         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5453         *buf = NULLCHAR;
5454         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5455         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5456         for(i = forwardMostMove; i<endPV; i++){
5457             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5458             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5459             k += strlen(buf+k);
5460         }
5461         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5462         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5463         endPV = savedEnd;
5464         return buf;
5465 }
5466
5467 Boolean
5468 LoadPV (int x, int y)
5469 { // called on right mouse click to load PV
5470   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5471   lastX = x; lastY = y;
5472   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5473   return TRUE;
5474 }
5475
5476 void
5477 UnLoadPV ()
5478 {
5479   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5480   if(endPV < 0) return;
5481   if(appData.autoCopyPV) CopyFENToClipboard();
5482   endPV = -1;
5483   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5484         Boolean saveAnimate = appData.animate;
5485         if(pushed) {
5486             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5487                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5488             } else storedGames--; // abandon shelved tail of original game
5489         }
5490         pushed = FALSE;
5491         forwardMostMove = currentMove;
5492         currentMove = oldFMM;
5493         appData.animate = FALSE;
5494         ToNrEvent(forwardMostMove);
5495         appData.animate = saveAnimate;
5496   }
5497   currentMove = forwardMostMove;
5498   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5499   ClearPremoveHighlights();
5500   DrawPosition(TRUE, boards[currentMove]);
5501 }
5502
5503 void
5504 MovePV (int x, int y, int h)
5505 { // step through PV based on mouse coordinates (called on mouse move)
5506   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5507
5508   // we must somehow check if right button is still down (might be released off board!)
5509   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5510   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5511   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5512   if(!step) return;
5513   lastX = x; lastY = y;
5514
5515   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5516   if(endPV < 0) return;
5517   if(y < margin) step = 1; else
5518   if(y > h - margin) step = -1;
5519   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5520   currentMove += step;
5521   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5522   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5523                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5524   DrawPosition(FALSE, boards[currentMove]);
5525 }
5526
5527
5528 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5529 // All positions will have equal probability, but the current method will not provide a unique
5530 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5531 #define DARK 1
5532 #define LITE 2
5533 #define ANY 3
5534
5535 int squaresLeft[4];
5536 int piecesLeft[(int)BlackPawn];
5537 int seed, nrOfShuffles;
5538
5539 void
5540 GetPositionNumber ()
5541 {       // sets global variable seed
5542         int i;
5543
5544         seed = appData.defaultFrcPosition;
5545         if(seed < 0) { // randomize based on time for negative FRC position numbers
5546                 for(i=0; i<50; i++) seed += random();
5547                 seed = random() ^ random() >> 8 ^ random() << 8;
5548                 if(seed<0) seed = -seed;
5549         }
5550 }
5551
5552 int
5553 put (Board board, int pieceType, int rank, int n, int shade)
5554 // put the piece on the (n-1)-th empty squares of the given shade
5555 {
5556         int i;
5557
5558         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5559                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5560                         board[rank][i] = (ChessSquare) pieceType;
5561                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5562                         squaresLeft[ANY]--;
5563                         piecesLeft[pieceType]--;
5564                         return i;
5565                 }
5566         }
5567         return -1;
5568 }
5569
5570
5571 void
5572 AddOnePiece (Board board, int pieceType, int rank, int shade)
5573 // calculate where the next piece goes, (any empty square), and put it there
5574 {
5575         int i;
5576
5577         i = seed % squaresLeft[shade];
5578         nrOfShuffles *= squaresLeft[shade];
5579         seed /= squaresLeft[shade];
5580         put(board, pieceType, rank, i, shade);
5581 }
5582
5583 void
5584 AddTwoPieces (Board board, int pieceType, int rank)
5585 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5586 {
5587         int i, n=squaresLeft[ANY], j=n-1, k;
5588
5589         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5590         i = seed % k;  // pick one
5591         nrOfShuffles *= k;
5592         seed /= k;
5593         while(i >= j) i -= j--;
5594         j = n - 1 - j; i += j;
5595         put(board, pieceType, rank, j, ANY);
5596         put(board, pieceType, rank, i, ANY);
5597 }
5598
5599 void
5600 SetUpShuffle (Board board, int number)
5601 {
5602         int i, p, first=1;
5603
5604         GetPositionNumber(); nrOfShuffles = 1;
5605
5606         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5607         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5608         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5609
5610         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5611
5612         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5613             p = (int) board[0][i];
5614             if(p < (int) BlackPawn) piecesLeft[p] ++;
5615             board[0][i] = EmptySquare;
5616         }
5617
5618         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5619             // shuffles restricted to allow normal castling put KRR first
5620             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5621                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5622             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5623                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5624             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5625                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5626             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5627                 put(board, WhiteRook, 0, 0, ANY);
5628             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5629         }
5630
5631         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5632             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5633             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5634                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5635                 while(piecesLeft[p] >= 2) {
5636                     AddOnePiece(board, p, 0, LITE);
5637                     AddOnePiece(board, p, 0, DARK);
5638                 }
5639                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5640             }
5641
5642         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5643             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5644             // but we leave King and Rooks for last, to possibly obey FRC restriction
5645             if(p == (int)WhiteRook) continue;
5646             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5647             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5648         }
5649
5650         // now everything is placed, except perhaps King (Unicorn) and Rooks
5651
5652         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5653             // Last King gets castling rights
5654             while(piecesLeft[(int)WhiteUnicorn]) {
5655                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5656                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5657             }
5658
5659             while(piecesLeft[(int)WhiteKing]) {
5660                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5661                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5662             }
5663
5664
5665         } else {
5666             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5667             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5668         }
5669
5670         // Only Rooks can be left; simply place them all
5671         while(piecesLeft[(int)WhiteRook]) {
5672                 i = put(board, WhiteRook, 0, 0, ANY);
5673                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5674                         if(first) {
5675                                 first=0;
5676                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5677                         }
5678                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5679                 }
5680         }
5681         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5682             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5683         }
5684
5685         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5686 }
5687
5688 int
5689 SetCharTable (char *table, const char * map)
5690 /* [HGM] moved here from winboard.c because of its general usefulness */
5691 /*       Basically a safe strcpy that uses the last character as King */
5692 {
5693     int result = FALSE; int NrPieces;
5694
5695     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5696                     && NrPieces >= 12 && !(NrPieces&1)) {
5697         int i; /* [HGM] Accept even length from 12 to 34 */
5698
5699         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5700         for( i=0; i<NrPieces/2-1; i++ ) {
5701             table[i] = map[i];
5702             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5703         }
5704         table[(int) WhiteKing]  = map[NrPieces/2-1];
5705         table[(int) BlackKing]  = map[NrPieces-1];
5706
5707         result = TRUE;
5708     }
5709
5710     return result;
5711 }
5712
5713 void
5714 Prelude (Board board)
5715 {       // [HGM] superchess: random selection of exo-pieces
5716         int i, j, k; ChessSquare p;
5717         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5718
5719         GetPositionNumber(); // use FRC position number
5720
5721         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5722             SetCharTable(pieceToChar, appData.pieceToCharTable);
5723             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5724                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5725         }
5726
5727         j = seed%4;                 seed /= 4;
5728         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5729         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5730         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5731         j = seed%3 + (seed%3 >= j); seed /= 3;
5732         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5733         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5734         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5735         j = seed%3;                 seed /= 3;
5736         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5737         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5738         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5739         j = seed%2 + (seed%2 >= j); seed /= 2;
5740         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5741         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5742         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5743         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5744         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5745         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5746         put(board, exoPieces[0],    0, 0, ANY);
5747         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5748 }
5749
5750 void
5751 InitPosition (int redraw)
5752 {
5753     ChessSquare (* pieces)[BOARD_FILES];
5754     int i, j, pawnRow, overrule,
5755     oldx = gameInfo.boardWidth,
5756     oldy = gameInfo.boardHeight,
5757     oldh = gameInfo.holdingsWidth;
5758     static int oldv;
5759
5760     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5761
5762     /* [AS] Initialize pv info list [HGM] and game status */
5763     {
5764         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5765             pvInfoList[i].depth = 0;
5766             boards[i][EP_STATUS] = EP_NONE;
5767             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5768         }
5769
5770         initialRulePlies = 0; /* 50-move counter start */
5771
5772         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5773         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5774     }
5775
5776
5777     /* [HGM] logic here is completely changed. In stead of full positions */
5778     /* the initialized data only consist of the two backranks. The switch */
5779     /* selects which one we will use, which is than copied to the Board   */
5780     /* initialPosition, which for the rest is initialized by Pawns and    */
5781     /* empty squares. This initial position is then copied to boards[0],  */
5782     /* possibly after shuffling, so that it remains available.            */
5783
5784     gameInfo.holdingsWidth = 0; /* default board sizes */
5785     gameInfo.boardWidth    = 8;
5786     gameInfo.boardHeight   = 8;
5787     gameInfo.holdingsSize  = 0;
5788     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5789     for(i=0; i<BOARD_FILES-2; i++)
5790       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5791     initialPosition[EP_STATUS] = EP_NONE;
5792     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5793     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5794          SetCharTable(pieceNickName, appData.pieceNickNames);
5795     else SetCharTable(pieceNickName, "............");
5796     pieces = FIDEArray;
5797
5798     switch (gameInfo.variant) {
5799     case VariantFischeRandom:
5800       shuffleOpenings = TRUE;
5801     default:
5802       break;
5803     case VariantShatranj:
5804       pieces = ShatranjArray;
5805       nrCastlingRights = 0;
5806       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5807       break;
5808     case VariantMakruk:
5809       pieces = makrukArray;
5810       nrCastlingRights = 0;
5811       startedFromSetupPosition = TRUE;
5812       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5813       break;
5814     case VariantTwoKings:
5815       pieces = twoKingsArray;
5816       break;
5817     case VariantGrand:
5818       pieces = GrandArray;
5819       nrCastlingRights = 0;
5820       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5821       gameInfo.boardWidth = 10;
5822       gameInfo.boardHeight = 10;
5823       gameInfo.holdingsSize = 7;
5824       break;
5825     case VariantCapaRandom:
5826       shuffleOpenings = TRUE;
5827     case VariantCapablanca:
5828       pieces = CapablancaArray;
5829       gameInfo.boardWidth = 10;
5830       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5831       break;
5832     case VariantGothic:
5833       pieces = GothicArray;
5834       gameInfo.boardWidth = 10;
5835       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5836       break;
5837     case VariantSChess:
5838       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5839       gameInfo.holdingsSize = 7;
5840       break;
5841     case VariantJanus:
5842       pieces = JanusArray;
5843       gameInfo.boardWidth = 10;
5844       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5845       nrCastlingRights = 6;
5846         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5847         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5848         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5849         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5850         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5851         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5852       break;
5853     case VariantFalcon:
5854       pieces = FalconArray;
5855       gameInfo.boardWidth = 10;
5856       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5857       break;
5858     case VariantXiangqi:
5859       pieces = XiangqiArray;
5860       gameInfo.boardWidth  = 9;
5861       gameInfo.boardHeight = 10;
5862       nrCastlingRights = 0;
5863       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5864       break;
5865     case VariantShogi:
5866       pieces = ShogiArray;
5867       gameInfo.boardWidth  = 9;
5868       gameInfo.boardHeight = 9;
5869       gameInfo.holdingsSize = 7;
5870       nrCastlingRights = 0;
5871       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5872       break;
5873     case VariantCourier:
5874       pieces = CourierArray;
5875       gameInfo.boardWidth  = 12;
5876       nrCastlingRights = 0;
5877       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5878       break;
5879     case VariantKnightmate:
5880       pieces = KnightmateArray;
5881       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5882       break;
5883     case VariantSpartan:
5884       pieces = SpartanArray;
5885       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5886       break;
5887     case VariantFairy:
5888       pieces = fairyArray;
5889       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5890       break;
5891     case VariantGreat:
5892       pieces = GreatArray;
5893       gameInfo.boardWidth = 10;
5894       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5895       gameInfo.holdingsSize = 8;
5896       break;
5897     case VariantSuper:
5898       pieces = FIDEArray;
5899       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5900       gameInfo.holdingsSize = 8;
5901       startedFromSetupPosition = TRUE;
5902       break;
5903     case VariantCrazyhouse:
5904     case VariantBughouse:
5905       pieces = FIDEArray;
5906       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5907       gameInfo.holdingsSize = 5;
5908       break;
5909     case VariantWildCastle:
5910       pieces = FIDEArray;
5911       /* !!?shuffle with kings guaranteed to be on d or e file */
5912       shuffleOpenings = 1;
5913       break;
5914     case VariantNoCastle:
5915       pieces = FIDEArray;
5916       nrCastlingRights = 0;
5917       /* !!?unconstrained back-rank shuffle */
5918       shuffleOpenings = 1;
5919       break;
5920     }
5921
5922     overrule = 0;
5923     if(appData.NrFiles >= 0) {
5924         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5925         gameInfo.boardWidth = appData.NrFiles;
5926     }
5927     if(appData.NrRanks >= 0) {
5928         gameInfo.boardHeight = appData.NrRanks;
5929     }
5930     if(appData.holdingsSize >= 0) {
5931         i = appData.holdingsSize;
5932         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5933         gameInfo.holdingsSize = i;
5934     }
5935     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5936     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5937         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5938
5939     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5940     if(pawnRow < 1) pawnRow = 1;
5941     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5942
5943     /* User pieceToChar list overrules defaults */
5944     if(appData.pieceToCharTable != NULL)
5945         SetCharTable(pieceToChar, appData.pieceToCharTable);
5946
5947     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5948
5949         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5950             s = (ChessSquare) 0; /* account holding counts in guard band */
5951         for( i=0; i<BOARD_HEIGHT; i++ )
5952             initialPosition[i][j] = s;
5953
5954         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5955         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5956         initialPosition[pawnRow][j] = WhitePawn;
5957         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5958         if(gameInfo.variant == VariantXiangqi) {
5959             if(j&1) {
5960                 initialPosition[pawnRow][j] =
5961                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5962                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5963                    initialPosition[2][j] = WhiteCannon;
5964                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5965                 }
5966             }
5967         }
5968         if(gameInfo.variant == VariantGrand) {
5969             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5970                initialPosition[0][j] = WhiteRook;
5971                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5972             }
5973         }
5974         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5975     }
5976     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5977
5978             j=BOARD_LEFT+1;
5979             initialPosition[1][j] = WhiteBishop;
5980             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5981             j=BOARD_RGHT-2;
5982             initialPosition[1][j] = WhiteRook;
5983             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5984     }
5985
5986     if( nrCastlingRights == -1) {
5987         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5988         /*       This sets default castling rights from none to normal corners   */
5989         /* Variants with other castling rights must set them themselves above    */
5990         nrCastlingRights = 6;
5991
5992         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5993         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5994         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5995         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5996         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5997         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5998      }
5999
6000      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6001      if(gameInfo.variant == VariantGreat) { // promotion commoners
6002         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6003         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6004         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6005         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6006      }
6007      if( gameInfo.variant == VariantSChess ) {
6008       initialPosition[1][0] = BlackMarshall;
6009       initialPosition[2][0] = BlackAngel;
6010       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6011       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6012       initialPosition[1][1] = initialPosition[2][1] = 
6013       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6014      }
6015   if (appData.debugMode) {
6016     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6017   }
6018     if(shuffleOpenings) {
6019         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6020         startedFromSetupPosition = TRUE;
6021     }
6022     if(startedFromPositionFile) {
6023       /* [HGM] loadPos: use PositionFile for every new game */
6024       CopyBoard(initialPosition, filePosition);
6025       for(i=0; i<nrCastlingRights; i++)
6026           initialRights[i] = filePosition[CASTLING][i];
6027       startedFromSetupPosition = TRUE;
6028     }
6029
6030     CopyBoard(boards[0], initialPosition);
6031
6032     if(oldx != gameInfo.boardWidth ||
6033        oldy != gameInfo.boardHeight ||
6034        oldv != gameInfo.variant ||
6035        oldh != gameInfo.holdingsWidth
6036                                          )
6037             InitDrawingSizes(-2 ,0);
6038
6039     oldv = gameInfo.variant;
6040     if (redraw)
6041       DrawPosition(TRUE, boards[currentMove]);
6042 }
6043
6044 void
6045 SendBoard (ChessProgramState *cps, int moveNum)
6046 {
6047     char message[MSG_SIZ];
6048
6049     if (cps->useSetboard) {
6050       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6051       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6052       SendToProgram(message, cps);
6053       free(fen);
6054
6055     } else {
6056       ChessSquare *bp;
6057       int i, j, left=0, right=BOARD_WIDTH;
6058       /* Kludge to set black to move, avoiding the troublesome and now
6059        * deprecated "black" command.
6060        */
6061       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6062         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6063
6064       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6065
6066       SendToProgram("edit\n", cps);
6067       SendToProgram("#\n", cps);
6068       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6069         bp = &boards[moveNum][i][left];
6070         for (j = left; j < right; j++, bp++) {
6071           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6072           if ((int) *bp < (int) BlackPawn) {
6073             if(j == BOARD_RGHT+1)
6074                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6075             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6076             if(message[0] == '+' || message[0] == '~') {
6077               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6078                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6079                         AAA + j, ONE + i);
6080             }
6081             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6082                 message[1] = BOARD_RGHT   - 1 - j + '1';
6083                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6084             }
6085             SendToProgram(message, cps);
6086           }
6087         }
6088       }
6089
6090       SendToProgram("c\n", cps);
6091       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6092         bp = &boards[moveNum][i][left];
6093         for (j = left; j < right; j++, bp++) {
6094           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6095           if (((int) *bp != (int) EmptySquare)
6096               && ((int) *bp >= (int) BlackPawn)) {
6097             if(j == BOARD_LEFT-2)
6098                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6099             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6100                     AAA + j, ONE + i);
6101             if(message[0] == '+' || message[0] == '~') {
6102               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6103                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6104                         AAA + j, ONE + i);
6105             }
6106             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6107                 message[1] = BOARD_RGHT   - 1 - j + '1';
6108                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6109             }
6110             SendToProgram(message, cps);
6111           }
6112         }
6113       }
6114
6115       SendToProgram(".\n", cps);
6116     }
6117     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6118 }
6119
6120 char exclusionHeader[MSG_SIZ];
6121 int exCnt, excludePtr;
6122 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6123 static Exclusion excluTab[200];
6124 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6125
6126 static void
6127 WriteMap (int s)
6128 {
6129     int j;
6130     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6131     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6132 }
6133
6134 static void
6135 ClearMap ()
6136 {
6137     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6138     excludePtr = 24; exCnt = 0;
6139     WriteMap(0);
6140 }
6141
6142 static void
6143 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6144 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6145     char buf[2*MOVE_LEN], *p;
6146     Exclusion *e = excluTab;
6147     int i;
6148     for(i=0; i<exCnt; i++)
6149         if(e[i].ff == fromX && e[i].fr == fromY &&
6150            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6151     if(i == exCnt) { // was not in exclude list; add it
6152         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6153         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6154             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6155             return; // abort
6156         }
6157         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6158         excludePtr++; e[i].mark = excludePtr++;
6159         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6160         exCnt++;
6161     }
6162     exclusionHeader[e[i].mark] = state;
6163 }
6164
6165 static int
6166 ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, char state)
6167 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6168     char buf[MSG_SIZ];
6169     int j, k;
6170     ChessMove moveType;
6171     if(promoChar == -1) { // kludge to indicate best move
6172         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6173             return 1; // if unparsable, abort
6174     }
6175     // update exclusion map (resolving toggle by consulting existing state)
6176     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6177     j = k%8; k >>= 3;
6178     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6179     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6180          excludeMap[k] |=   1<<j;
6181     else excludeMap[k] &= ~(1<<j);
6182     // update header
6183     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6184     // inform engine
6185     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6186     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6187     SendToProgram(buf, &first);
6188     return (state == '+');
6189 }
6190
6191 static void
6192 ExcludeClick (int index)
6193 {
6194     int i, j;
6195     Exclusion *e = excluTab;
6196     if(index < 25) { // none, best or tail clicked
6197         if(index < 13) { // none: include all
6198             WriteMap(0); // clear map
6199             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6200             SendToProgram("include all\n", &first); // and inform engine
6201         } else if(index > 18) { // tail
6202             if(exclusionHeader[19] == '-') { // tail was excluded
6203                 SendToProgram("include all\n", &first);
6204                 WriteMap(0); // clear map completely
6205                 // now re-exclude selected moves
6206                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6207                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6208             } else { // tail was included or in mixed state
6209                 SendToProgram("exclude all\n", &first);
6210                 WriteMap(0xFF); // fill map completely
6211                 // now re-include selected moves
6212                 j = 0; // count them
6213                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6214                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6215                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6216             }
6217         } else { // best
6218             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6219         }
6220     } else {
6221         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6222             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6223             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6224             break;
6225         }
6226     }
6227 }
6228
6229 ChessSquare
6230 DefaultPromoChoice (int white)
6231 {
6232     ChessSquare result;
6233     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6234         result = WhiteFerz; // no choice
6235     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6236         result= WhiteKing; // in Suicide Q is the last thing we want
6237     else if(gameInfo.variant == VariantSpartan)
6238         result = white ? WhiteQueen : WhiteAngel;
6239     else result = WhiteQueen;
6240     if(!white) result = WHITE_TO_BLACK result;
6241     return result;
6242 }
6243
6244 static int autoQueen; // [HGM] oneclick
6245
6246 int
6247 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6248 {
6249     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6250     /* [HGM] add Shogi promotions */
6251     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6252     ChessSquare piece;
6253     ChessMove moveType;
6254     Boolean premove;
6255
6256     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6257     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6258
6259     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6260       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6261         return FALSE;
6262
6263     piece = boards[currentMove][fromY][fromX];
6264     if(gameInfo.variant == VariantShogi) {
6265         promotionZoneSize = BOARD_HEIGHT/3;
6266         highestPromotingPiece = (int)WhiteFerz;
6267     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6268         promotionZoneSize = 3;
6269     }
6270
6271     // Treat Lance as Pawn when it is not representing Amazon
6272     if(gameInfo.variant != VariantSuper) {
6273         if(piece == WhiteLance) piece = WhitePawn; else
6274         if(piece == BlackLance) piece = BlackPawn;
6275     }
6276
6277     // next weed out all moves that do not touch the promotion zone at all
6278     if((int)piece >= BlackPawn) {
6279         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6280              return FALSE;
6281         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6282     } else {
6283         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6284            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6285     }
6286
6287     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6288
6289     // weed out mandatory Shogi promotions
6290     if(gameInfo.variant == VariantShogi) {
6291         if(piece >= BlackPawn) {
6292             if(toY == 0 && piece == BlackPawn ||
6293                toY == 0 && piece == BlackQueen ||
6294                toY <= 1 && piece == BlackKnight) {
6295                 *promoChoice = '+';
6296                 return FALSE;
6297             }
6298         } else {
6299             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6300                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6301                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6302                 *promoChoice = '+';
6303                 return FALSE;
6304             }
6305         }
6306     }
6307
6308     // weed out obviously illegal Pawn moves
6309     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6310         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6311         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6312         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6313         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6314         // note we are not allowed to test for valid (non-)capture, due to premove
6315     }
6316
6317     // we either have a choice what to promote to, or (in Shogi) whether to promote
6318     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6319         *promoChoice = PieceToChar(BlackFerz);  // no choice
6320         return FALSE;
6321     }
6322     // no sense asking what we must promote to if it is going to explode...
6323     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6324         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6325         return FALSE;
6326     }
6327     // give caller the default choice even if we will not make it
6328     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6329     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6330     if(        sweepSelect && gameInfo.variant != VariantGreat
6331                            && gameInfo.variant != VariantGrand
6332                            && gameInfo.variant != VariantSuper) return FALSE;
6333     if(autoQueen) return FALSE; // predetermined
6334
6335     // suppress promotion popup on illegal moves that are not premoves
6336     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6337               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6338     if(appData.testLegality && !premove) {
6339         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6340                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6341         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6342             return FALSE;
6343     }
6344
6345     return TRUE;
6346 }
6347
6348 int
6349 InPalace (int row, int column)
6350 {   /* [HGM] for Xiangqi */
6351     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6352          column < (BOARD_WIDTH + 4)/2 &&
6353          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6354     return FALSE;
6355 }
6356
6357 int
6358 PieceForSquare (int x, int y)
6359 {
6360   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6361      return -1;
6362   else
6363      return boards[currentMove][y][x];
6364 }
6365
6366 int
6367 OKToStartUserMove (int x, int y)
6368 {
6369     ChessSquare from_piece;
6370     int white_piece;
6371
6372     if (matchMode) return FALSE;
6373     if (gameMode == EditPosition) return TRUE;
6374
6375     if (x >= 0 && y >= 0)
6376       from_piece = boards[currentMove][y][x];
6377     else
6378       from_piece = EmptySquare;
6379
6380     if (from_piece == EmptySquare) return FALSE;
6381
6382     white_piece = (int)from_piece >= (int)WhitePawn &&
6383       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6384
6385     switch (gameMode) {
6386       case AnalyzeFile:
6387       case TwoMachinesPlay:
6388       case EndOfGame:
6389         return FALSE;
6390
6391       case IcsObserving:
6392       case IcsIdle:
6393         return FALSE;
6394
6395       case MachinePlaysWhite:
6396       case IcsPlayingBlack:
6397         if (appData.zippyPlay) return FALSE;
6398         if (white_piece) {
6399             DisplayMoveError(_("You are playing Black"));
6400             return FALSE;
6401         }
6402         break;
6403
6404       case MachinePlaysBlack:
6405       case IcsPlayingWhite:
6406         if (appData.zippyPlay) return FALSE;
6407         if (!white_piece) {
6408             DisplayMoveError(_("You are playing White"));
6409             return FALSE;
6410         }
6411         break;
6412
6413       case PlayFromGameFile:
6414             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6415       case EditGame:
6416         if (!white_piece && WhiteOnMove(currentMove)) {
6417             DisplayMoveError(_("It is White's turn"));
6418             return FALSE;
6419         }
6420         if (white_piece && !WhiteOnMove(currentMove)) {
6421             DisplayMoveError(_("It is Black's turn"));
6422             return FALSE;
6423         }
6424         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6425             /* Editing correspondence game history */
6426             /* Could disallow this or prompt for confirmation */
6427             cmailOldMove = -1;
6428         }
6429         break;
6430
6431       case BeginningOfGame:
6432         if (appData.icsActive) return FALSE;
6433         if (!appData.noChessProgram) {
6434             if (!white_piece) {
6435                 DisplayMoveError(_("You are playing White"));
6436                 return FALSE;
6437             }
6438         }
6439         break;
6440
6441       case Training:
6442         if (!white_piece && WhiteOnMove(currentMove)) {
6443             DisplayMoveError(_("It is White's turn"));
6444             return FALSE;
6445         }
6446         if (white_piece && !WhiteOnMove(currentMove)) {
6447             DisplayMoveError(_("It is Black's turn"));
6448             return FALSE;
6449         }
6450         break;
6451
6452       default:
6453       case IcsExamining:
6454         break;
6455     }
6456     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6457         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6458         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6459         && gameMode != AnalyzeFile && gameMode != Training) {
6460         DisplayMoveError(_("Displayed position is not current"));
6461         return FALSE;
6462     }
6463     return TRUE;
6464 }
6465
6466 Boolean
6467 OnlyMove (int *x, int *y, Boolean captures) 
6468 {
6469     DisambiguateClosure cl;
6470     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6471     switch(gameMode) {
6472       case MachinePlaysBlack:
6473       case IcsPlayingWhite:
6474       case BeginningOfGame:
6475         if(!WhiteOnMove(currentMove)) return FALSE;
6476         break;
6477       case MachinePlaysWhite:
6478       case IcsPlayingBlack:
6479         if(WhiteOnMove(currentMove)) return FALSE;
6480         break;
6481       case EditGame:
6482         break;
6483       default:
6484         return FALSE;
6485     }
6486     cl.pieceIn = EmptySquare;
6487     cl.rfIn = *y;
6488     cl.ffIn = *x;
6489     cl.rtIn = -1;
6490     cl.ftIn = -1;
6491     cl.promoCharIn = NULLCHAR;
6492     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6493     if( cl.kind == NormalMove ||
6494         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6495         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6496         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6497       fromX = cl.ff;
6498       fromY = cl.rf;
6499       *x = cl.ft;
6500       *y = cl.rt;
6501       return TRUE;
6502     }
6503     if(cl.kind != ImpossibleMove) return FALSE;
6504     cl.pieceIn = EmptySquare;
6505     cl.rfIn = -1;
6506     cl.ffIn = -1;
6507     cl.rtIn = *y;
6508     cl.ftIn = *x;
6509     cl.promoCharIn = NULLCHAR;
6510     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6511     if( cl.kind == NormalMove ||
6512         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6513         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6514         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6515       fromX = cl.ff;
6516       fromY = cl.rf;
6517       *x = cl.ft;
6518       *y = cl.rt;
6519       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6520       return TRUE;
6521     }
6522     return FALSE;
6523 }
6524
6525 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6526 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6527 int lastLoadGameUseList = FALSE;
6528 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6529 ChessMove lastLoadGameStart = EndOfFile;
6530 int doubleClick;
6531
6532 void
6533 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6534 {
6535     ChessMove moveType;
6536     ChessSquare pdown, pup;
6537     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6538
6539
6540     /* Check if the user is playing in turn.  This is complicated because we
6541        let the user "pick up" a piece before it is his turn.  So the piece he
6542        tried to pick up may have been captured by the time he puts it down!
6543        Therefore we use the color the user is supposed to be playing in this
6544        test, not the color of the piece that is currently on the starting
6545        square---except in EditGame mode, where the user is playing both
6546        sides; fortunately there the capture race can't happen.  (It can
6547        now happen in IcsExamining mode, but that's just too bad.  The user
6548        will get a somewhat confusing message in that case.)
6549        */
6550
6551     switch (gameMode) {
6552       case AnalyzeFile:
6553       case TwoMachinesPlay:
6554       case EndOfGame:
6555       case IcsObserving:
6556       case IcsIdle:
6557         /* We switched into a game mode where moves are not accepted,
6558            perhaps while the mouse button was down. */
6559         return;
6560
6561       case MachinePlaysWhite:
6562         /* User is moving for Black */
6563         if (WhiteOnMove(currentMove)) {
6564             DisplayMoveError(_("It is White's turn"));
6565             return;
6566         }
6567         break;
6568
6569       case MachinePlaysBlack:
6570         /* User is moving for White */
6571         if (!WhiteOnMove(currentMove)) {
6572             DisplayMoveError(_("It is Black's turn"));
6573             return;
6574         }
6575         break;
6576
6577       case PlayFromGameFile:
6578             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6579       case EditGame:
6580       case IcsExamining:
6581       case BeginningOfGame:
6582       case AnalyzeMode:
6583       case Training:
6584         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6585         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6586             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6587             /* User is moving for Black */
6588             if (WhiteOnMove(currentMove)) {
6589                 DisplayMoveError(_("It is White's turn"));
6590                 return;
6591             }
6592         } else {
6593             /* User is moving for White */
6594             if (!WhiteOnMove(currentMove)) {
6595                 DisplayMoveError(_("It is Black's turn"));
6596                 return;
6597             }
6598         }
6599         break;
6600
6601       case IcsPlayingBlack:
6602         /* User is moving for Black */
6603         if (WhiteOnMove(currentMove)) {
6604             if (!appData.premove) {
6605                 DisplayMoveError(_("It is White's turn"));
6606             } else if (toX >= 0 && toY >= 0) {
6607                 premoveToX = toX;
6608                 premoveToY = toY;
6609                 premoveFromX = fromX;
6610                 premoveFromY = fromY;
6611                 premovePromoChar = promoChar;
6612                 gotPremove = 1;
6613                 if (appData.debugMode)
6614                     fprintf(debugFP, "Got premove: fromX %d,"
6615                             "fromY %d, toX %d, toY %d\n",
6616                             fromX, fromY, toX, toY);
6617             }
6618             return;
6619         }
6620         break;
6621
6622       case IcsPlayingWhite:
6623         /* User is moving for White */
6624         if (!WhiteOnMove(currentMove)) {
6625             if (!appData.premove) {
6626                 DisplayMoveError(_("It is Black's turn"));
6627             } else if (toX >= 0 && toY >= 0) {
6628                 premoveToX = toX;
6629                 premoveToY = toY;
6630                 premoveFromX = fromX;
6631                 premoveFromY = fromY;
6632                 premovePromoChar = promoChar;
6633                 gotPremove = 1;
6634                 if (appData.debugMode)
6635                     fprintf(debugFP, "Got premove: fromX %d,"
6636                             "fromY %d, toX %d, toY %d\n",
6637                             fromX, fromY, toX, toY);
6638             }
6639             return;
6640         }
6641         break;
6642
6643       default:
6644         break;
6645
6646       case EditPosition:
6647         /* EditPosition, empty square, or different color piece;
6648            click-click move is possible */
6649         if (toX == -2 || toY == -2) {
6650             boards[0][fromY][fromX] = EmptySquare;
6651             DrawPosition(FALSE, boards[currentMove]);
6652             return;
6653         } else if (toX >= 0 && toY >= 0) {
6654             boards[0][toY][toX] = boards[0][fromY][fromX];
6655             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6656                 if(boards[0][fromY][0] != EmptySquare) {
6657                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6658                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6659                 }
6660             } else
6661             if(fromX == BOARD_RGHT+1) {
6662                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6663                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6664                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6665                 }
6666             } else
6667             boards[0][fromY][fromX] = EmptySquare;
6668             DrawPosition(FALSE, boards[currentMove]);
6669             return;
6670         }
6671         return;
6672     }
6673
6674     if(toX < 0 || toY < 0) return;
6675     pdown = boards[currentMove][fromY][fromX];
6676     pup = boards[currentMove][toY][toX];
6677
6678     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6679     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6680          if( pup != EmptySquare ) return;
6681          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6682            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6683                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6684            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6685            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6686            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6687            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6688          fromY = DROP_RANK;
6689     }
6690
6691     /* [HGM] always test for legality, to get promotion info */
6692     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6693                                          fromY, fromX, toY, toX, promoChar);
6694
6695     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6696
6697     /* [HGM] but possibly ignore an IllegalMove result */
6698     if (appData.testLegality) {
6699         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6700             DisplayMoveError(_("Illegal move"));
6701             return;
6702         }
6703     }
6704
6705     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6706         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6707              ClearPremoveHighlights(); // was included
6708         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6709         return;
6710     }
6711
6712     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6713 }
6714
6715 /* Common tail of UserMoveEvent and DropMenuEvent */
6716 int
6717 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6718 {
6719     char *bookHit = 0;
6720
6721     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6722         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6723         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6724         if(WhiteOnMove(currentMove)) {
6725             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6726         } else {
6727             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6728         }
6729     }
6730
6731     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6732        move type in caller when we know the move is a legal promotion */
6733     if(moveType == NormalMove && promoChar)
6734         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6735
6736     /* [HGM] <popupFix> The following if has been moved here from
6737        UserMoveEvent(). Because it seemed to belong here (why not allow
6738        piece drops in training games?), and because it can only be
6739        performed after it is known to what we promote. */
6740     if (gameMode == Training) {
6741       /* compare the move played on the board to the next move in the
6742        * game. If they match, display the move and the opponent's response.
6743        * If they don't match, display an error message.
6744        */
6745       int saveAnimate;
6746       Board testBoard;
6747       CopyBoard(testBoard, boards[currentMove]);
6748       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6749
6750       if (CompareBoards(testBoard, boards[currentMove+1])) {
6751         ForwardInner(currentMove+1);
6752
6753         /* Autoplay the opponent's response.
6754          * if appData.animate was TRUE when Training mode was entered,
6755          * the response will be animated.
6756          */
6757         saveAnimate = appData.animate;
6758         appData.animate = animateTraining;
6759         ForwardInner(currentMove+1);
6760         appData.animate = saveAnimate;
6761
6762         /* check for the end of the game */
6763         if (currentMove >= forwardMostMove) {
6764           gameMode = PlayFromGameFile;
6765           ModeHighlight();
6766           SetTrainingModeOff();
6767           DisplayInformation(_("End of game"));
6768         }
6769       } else {
6770         DisplayError(_("Incorrect move"), 0);
6771       }
6772       return 1;
6773     }
6774
6775   /* Ok, now we know that the move is good, so we can kill
6776      the previous line in Analysis Mode */
6777   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6778                                 && currentMove < forwardMostMove) {
6779     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6780     else forwardMostMove = currentMove;
6781   }
6782
6783   ClearMap();
6784
6785   /* If we need the chess program but it's dead, restart it */
6786   ResurrectChessProgram();
6787
6788   /* A user move restarts a paused game*/
6789   if (pausing)
6790     PauseEvent();
6791
6792   thinkOutput[0] = NULLCHAR;
6793
6794   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6795
6796   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6797     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6798     return 1;
6799   }
6800
6801   if (gameMode == BeginningOfGame) {
6802     if (appData.noChessProgram) {
6803       gameMode = EditGame;
6804       SetGameInfo();
6805     } else {
6806       char buf[MSG_SIZ];
6807       gameMode = MachinePlaysBlack;
6808       StartClocks();
6809       SetGameInfo();
6810       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6811       DisplayTitle(buf);
6812       if (first.sendName) {
6813         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6814         SendToProgram(buf, &first);
6815       }
6816       StartClocks();
6817     }
6818     ModeHighlight();
6819   }
6820
6821   /* Relay move to ICS or chess engine */
6822   if (appData.icsActive) {
6823     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6824         gameMode == IcsExamining) {
6825       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6826         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6827         SendToICS("draw ");
6828         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6829       }
6830       // also send plain move, in case ICS does not understand atomic claims
6831       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6832       ics_user_moved = 1;
6833     }
6834   } else {
6835     if (first.sendTime && (gameMode == BeginningOfGame ||
6836                            gameMode == MachinePlaysWhite ||
6837                            gameMode == MachinePlaysBlack)) {
6838       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6839     }
6840     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6841          // [HGM] book: if program might be playing, let it use book
6842         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6843         first.maybeThinking = TRUE;
6844     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6845         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6846         SendBoard(&first, currentMove+1);
6847     } else SendMoveToProgram(forwardMostMove-1, &first);
6848     if (currentMove == cmailOldMove + 1) {
6849       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6850     }
6851   }
6852
6853   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6854
6855   switch (gameMode) {
6856   case EditGame:
6857     if(appData.testLegality)
6858     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6859     case MT_NONE:
6860     case MT_CHECK:
6861       break;
6862     case MT_CHECKMATE:
6863     case MT_STAINMATE:
6864       if (WhiteOnMove(currentMove)) {
6865         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6866       } else {
6867         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6868       }
6869       break;
6870     case MT_STALEMATE:
6871       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6872       break;
6873     }
6874     break;
6875
6876   case MachinePlaysBlack:
6877   case MachinePlaysWhite:
6878     /* disable certain menu options while machine is thinking */
6879     SetMachineThinkingEnables();
6880     break;
6881
6882   default:
6883     break;
6884   }
6885
6886   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6887   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6888
6889   if(bookHit) { // [HGM] book: simulate book reply
6890         static char bookMove[MSG_SIZ]; // a bit generous?
6891
6892         programStats.nodes = programStats.depth = programStats.time =
6893         programStats.score = programStats.got_only_move = 0;
6894         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6895
6896         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6897         strcat(bookMove, bookHit);
6898         HandleMachineMove(bookMove, &first);
6899   }
6900   return 1;
6901 }
6902
6903 void
6904 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6905 {
6906     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6907     Markers *m = (Markers *) closure;
6908     if(rf == fromY && ff == fromX)
6909         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6910                          || kind == WhiteCapturesEnPassant
6911                          || kind == BlackCapturesEnPassant);
6912     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6913 }
6914
6915 void
6916 MarkTargetSquares (int clear)
6917 {
6918   int x, y;
6919   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6920      !appData.testLegality || gameMode == EditPosition) return;
6921   if(clear) {
6922     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6923   } else {
6924     int capt = 0;
6925     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6926     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6927       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6928       if(capt)
6929       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6930     }
6931   }
6932   DrawPosition(TRUE, NULL);
6933 }
6934
6935 int
6936 Explode (Board board, int fromX, int fromY, int toX, int toY)
6937 {
6938     if(gameInfo.variant == VariantAtomic &&
6939        (board[toY][toX] != EmptySquare ||                     // capture?
6940         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6941                          board[fromY][fromX] == BlackPawn   )
6942       )) {
6943         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6944         return TRUE;
6945     }
6946     return FALSE;
6947 }
6948
6949 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6950
6951 int
6952 CanPromote (ChessSquare piece, int y)
6953 {
6954         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6955         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6956         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6957            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6958            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6959                                                   gameInfo.variant == VariantMakruk) return FALSE;
6960         return (piece == BlackPawn && y == 1 ||
6961                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6962                 piece == BlackLance && y == 1 ||
6963                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6964 }
6965
6966 void
6967 LeftClick (ClickType clickType, int xPix, int yPix)
6968 {
6969     int x, y;
6970     Boolean saveAnimate;
6971     static int second = 0, promotionChoice = 0, clearFlag = 0;
6972     char promoChoice = NULLCHAR;
6973     ChessSquare piece;
6974     static TimeMark lastClickTime, prevClickTime;
6975
6976     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
6977
6978     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
6979
6980     if (clickType == Press) ErrorPopDown();
6981
6982     x = EventToSquare(xPix, BOARD_WIDTH);
6983     y = EventToSquare(yPix, BOARD_HEIGHT);
6984     if (!flipView && y >= 0) {
6985         y = BOARD_HEIGHT - 1 - y;
6986     }
6987     if (flipView && x >= 0) {
6988         x = BOARD_WIDTH - 1 - x;
6989     }
6990
6991     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6992         defaultPromoChoice = promoSweep;
6993         promoSweep = EmptySquare;   // terminate sweep
6994         promoDefaultAltered = TRUE;
6995         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6996     }
6997
6998     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6999         if(clickType == Release) return; // ignore upclick of click-click destination
7000         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7001         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7002         if(gameInfo.holdingsWidth &&
7003                 (WhiteOnMove(currentMove)
7004                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7005                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7006             // click in right holdings, for determining promotion piece
7007             ChessSquare p = boards[currentMove][y][x];
7008             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7009             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7010             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7011                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7012                 fromX = fromY = -1;
7013                 return;
7014             }
7015         }
7016         DrawPosition(FALSE, boards[currentMove]);
7017         return;
7018     }
7019
7020     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7021     if(clickType == Press
7022             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7023               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7024               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7025         return;
7026
7027     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7028         // could be static click on premove from-square: abort premove
7029         gotPremove = 0;
7030         ClearPremoveHighlights();
7031     }
7032
7033     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7034         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7035
7036     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7037         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7038                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7039         defaultPromoChoice = DefaultPromoChoice(side);
7040     }
7041
7042     autoQueen = appData.alwaysPromoteToQueen;
7043
7044     if (fromX == -1) {
7045       int originalY = y;
7046       gatingPiece = EmptySquare;
7047       if (clickType != Press) {
7048         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7049             DragPieceEnd(xPix, yPix); dragging = 0;
7050             DrawPosition(FALSE, NULL);
7051         }
7052         return;
7053       }
7054       doubleClick = FALSE;
7055       fromX = x; fromY = y; toX = toY = -1;
7056       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7057          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7058          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7059             /* First square */
7060             if (OKToStartUserMove(fromX, fromY)) {
7061                 second = 0;
7062                 MarkTargetSquares(0);
7063                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7064                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7065                     promoSweep = defaultPromoChoice;
7066                     selectFlag = 0; lastX = xPix; lastY = yPix;
7067                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7068                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7069                 }
7070                 if (appData.highlightDragging) {
7071                     SetHighlights(fromX, fromY, -1, -1);
7072                 }
7073             } else fromX = fromY = -1;
7074             return;
7075         }
7076     }
7077
7078     /* fromX != -1 */
7079     if (clickType == Press && gameMode != EditPosition) {
7080         ChessSquare fromP;
7081         ChessSquare toP;
7082         int frc;
7083
7084         // ignore off-board to clicks
7085         if(y < 0 || x < 0) return;
7086
7087         /* Check if clicking again on the same color piece */
7088         fromP = boards[currentMove][fromY][fromX];
7089         toP = boards[currentMove][y][x];
7090         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7091         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7092              WhitePawn <= toP && toP <= WhiteKing &&
7093              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7094              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7095             (BlackPawn <= fromP && fromP <= BlackKing &&
7096              BlackPawn <= toP && toP <= BlackKing &&
7097              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7098              !(fromP == BlackKing && toP == BlackRook && frc))) {
7099             /* Clicked again on same color piece -- changed his mind */
7100             second = (x == fromX && y == fromY);
7101             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7102                 second = FALSE; // first double-click rather than scond click
7103                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7104             }
7105             promoDefaultAltered = FALSE;
7106             MarkTargetSquares(1);
7107            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7108             if (appData.highlightDragging) {
7109                 SetHighlights(x, y, -1, -1);
7110             } else {
7111                 ClearHighlights();
7112             }
7113             if (OKToStartUserMove(x, y)) {
7114                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7115                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7116                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7117                  gatingPiece = boards[currentMove][fromY][fromX];
7118                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7119                 fromX = x;
7120                 fromY = y; dragging = 1;
7121                 MarkTargetSquares(0);
7122                 DragPieceBegin(xPix, yPix, FALSE);
7123                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7124                     promoSweep = defaultPromoChoice;
7125                     selectFlag = 0; lastX = xPix; lastY = yPix;
7126                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7127                 }
7128             }
7129            }
7130            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7131            second = FALSE; 
7132         }
7133         // ignore clicks on holdings
7134         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7135     }
7136
7137     if (clickType == Release && x == fromX && y == fromY) {
7138         DragPieceEnd(xPix, yPix); dragging = 0;
7139         if(clearFlag) {
7140             // a deferred attempt to click-click move an empty square on top of a piece
7141             boards[currentMove][y][x] = EmptySquare;
7142             ClearHighlights();
7143             DrawPosition(FALSE, boards[currentMove]);
7144             fromX = fromY = -1; clearFlag = 0;
7145             return;
7146         }
7147         if (appData.animateDragging) {
7148             /* Undo animation damage if any */
7149             DrawPosition(FALSE, NULL);
7150         }
7151         if (second) {
7152             /* Second up/down in same square; just abort move */
7153             second = 0;
7154             fromX = fromY = -1;
7155             gatingPiece = EmptySquare;
7156             ClearHighlights();
7157             gotPremove = 0;
7158             ClearPremoveHighlights();
7159         } else {
7160             /* First upclick in same square; start click-click mode */
7161             SetHighlights(x, y, -1, -1);
7162         }
7163         return;
7164     }
7165
7166     clearFlag = 0;
7167
7168     /* we now have a different from- and (possibly off-board) to-square */
7169     /* Completed move */
7170     toX = x;
7171     toY = y;
7172     saveAnimate = appData.animate;
7173     MarkTargetSquares(1);
7174     if (clickType == Press) {
7175         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7176             // must be Edit Position mode with empty-square selected
7177             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7178             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7179             return;
7180         }
7181         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7182             ChessSquare piece = boards[currentMove][fromY][fromX];
7183             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7184             promoSweep = defaultPromoChoice;
7185             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7186             selectFlag = 0; lastX = xPix; lastY = yPix;
7187             Sweep(0); // Pawn that is going to promote: preview promotion piece
7188             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7189             DrawPosition(FALSE, boards[currentMove]);
7190             return;
7191         }
7192         /* Finish clickclick move */
7193         if (appData.animate || appData.highlightLastMove) {
7194             SetHighlights(fromX, fromY, toX, toY);
7195         } else {
7196             ClearHighlights();
7197         }
7198     } else {
7199         /* Finish drag move */
7200         if (appData.highlightLastMove) {
7201             SetHighlights(fromX, fromY, toX, toY);
7202         } else {
7203             ClearHighlights();
7204         }
7205         DragPieceEnd(xPix, yPix); dragging = 0;
7206         /* Don't animate move and drag both */
7207         appData.animate = FALSE;
7208     }
7209
7210     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7211     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7212         ChessSquare piece = boards[currentMove][fromY][fromX];
7213         if(gameMode == EditPosition && piece != EmptySquare &&
7214            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7215             int n;
7216
7217             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7218                 n = PieceToNumber(piece - (int)BlackPawn);
7219                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7220                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7221                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7222             } else
7223             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7224                 n = PieceToNumber(piece);
7225                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7226                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7227                 boards[currentMove][n][BOARD_WIDTH-2]++;
7228             }
7229             boards[currentMove][fromY][fromX] = EmptySquare;
7230         }
7231         ClearHighlights();
7232         fromX = fromY = -1;
7233         DrawPosition(TRUE, boards[currentMove]);
7234         return;
7235     }
7236
7237     // off-board moves should not be highlighted
7238     if(x < 0 || y < 0) ClearHighlights();
7239
7240     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7241
7242     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7243         SetHighlights(fromX, fromY, toX, toY);
7244         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7245             // [HGM] super: promotion to captured piece selected from holdings
7246             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7247             promotionChoice = TRUE;
7248             // kludge follows to temporarily execute move on display, without promoting yet
7249             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7250             boards[currentMove][toY][toX] = p;
7251             DrawPosition(FALSE, boards[currentMove]);
7252             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7253             boards[currentMove][toY][toX] = q;
7254             DisplayMessage("Click in holdings to choose piece", "");
7255             return;
7256         }
7257         PromotionPopUp();
7258     } else {
7259         int oldMove = currentMove;
7260         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7261         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7262         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7263         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7264            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7265             DrawPosition(TRUE, boards[currentMove]);
7266         fromX = fromY = -1;
7267     }
7268     appData.animate = saveAnimate;
7269     if (appData.animate || appData.animateDragging) {
7270         /* Undo animation damage if needed */
7271         DrawPosition(FALSE, NULL);
7272     }
7273 }
7274
7275 int
7276 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7277 {   // front-end-free part taken out of PieceMenuPopup
7278     int whichMenu; int xSqr, ySqr;
7279
7280     if(seekGraphUp) { // [HGM] seekgraph
7281         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7282         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7283         return -2;
7284     }
7285
7286     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7287          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7288         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7289         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7290         if(action == Press)   {
7291             originalFlip = flipView;
7292             flipView = !flipView; // temporarily flip board to see game from partners perspective
7293             DrawPosition(TRUE, partnerBoard);
7294             DisplayMessage(partnerStatus, "");
7295             partnerUp = TRUE;
7296         } else if(action == Release) {
7297             flipView = originalFlip;
7298             DrawPosition(TRUE, boards[currentMove]);
7299             partnerUp = FALSE;
7300         }
7301         return -2;
7302     }
7303
7304     xSqr = EventToSquare(x, BOARD_WIDTH);
7305     ySqr = EventToSquare(y, BOARD_HEIGHT);
7306     if (action == Release) {
7307         if(pieceSweep != EmptySquare) {
7308             EditPositionMenuEvent(pieceSweep, toX, toY);
7309             pieceSweep = EmptySquare;
7310         } else UnLoadPV(); // [HGM] pv
7311     }
7312     if (action != Press) return -2; // return code to be ignored
7313     switch (gameMode) {
7314       case IcsExamining:
7315         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7316       case EditPosition:
7317         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7318         if (xSqr < 0 || ySqr < 0) return -1;
7319         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7320         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7321         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7322         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7323         NextPiece(0);
7324         return 2; // grab
7325       case IcsObserving:
7326         if(!appData.icsEngineAnalyze) return -1;
7327       case IcsPlayingWhite:
7328       case IcsPlayingBlack:
7329         if(!appData.zippyPlay) goto noZip;
7330       case AnalyzeMode:
7331       case AnalyzeFile:
7332       case MachinePlaysWhite:
7333       case MachinePlaysBlack:
7334       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7335         if (!appData.dropMenu) {
7336           LoadPV(x, y);
7337           return 2; // flag front-end to grab mouse events
7338         }
7339         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7340            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7341       case EditGame:
7342       noZip:
7343         if (xSqr < 0 || ySqr < 0) return -1;
7344         if (!appData.dropMenu || appData.testLegality &&
7345             gameInfo.variant != VariantBughouse &&
7346             gameInfo.variant != VariantCrazyhouse) return -1;
7347         whichMenu = 1; // drop menu
7348         break;
7349       default:
7350         return -1;
7351     }
7352
7353     if (((*fromX = xSqr) < 0) ||
7354         ((*fromY = ySqr) < 0)) {
7355         *fromX = *fromY = -1;
7356         return -1;
7357     }
7358     if (flipView)
7359       *fromX = BOARD_WIDTH - 1 - *fromX;
7360     else
7361       *fromY = BOARD_HEIGHT - 1 - *fromY;
7362
7363     return whichMenu;
7364 }
7365
7366 void
7367 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7368 {
7369 //    char * hint = lastHint;
7370     FrontEndProgramStats stats;
7371
7372     stats.which = cps == &first ? 0 : 1;
7373     stats.depth = cpstats->depth;
7374     stats.nodes = cpstats->nodes;
7375     stats.score = cpstats->score;
7376     stats.time = cpstats->time;
7377     stats.pv = cpstats->movelist;
7378     stats.hint = lastHint;
7379     stats.an_move_index = 0;
7380     stats.an_move_count = 0;
7381
7382     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7383         stats.hint = cpstats->move_name;
7384         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7385         stats.an_move_count = cpstats->nr_moves;
7386     }
7387
7388     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
7389
7390     SetProgramStats( &stats );
7391 }
7392
7393 void
7394 ClearEngineOutputPane (int which)
7395 {
7396     static FrontEndProgramStats dummyStats;
7397     dummyStats.which = which;
7398     dummyStats.pv = "#";
7399     SetProgramStats( &dummyStats );
7400 }
7401
7402 #define MAXPLAYERS 500
7403
7404 char *
7405 TourneyStandings (int display)
7406 {
7407     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7408     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7409     char result, *p, *names[MAXPLAYERS];
7410
7411     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7412         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7413     names[0] = p = strdup(appData.participants);
7414     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7415
7416     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7417
7418     while(result = appData.results[nr]) {
7419         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7420         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7421         wScore = bScore = 0;
7422         switch(result) {
7423           case '+': wScore = 2; break;
7424           case '-': bScore = 2; break;
7425           case '=': wScore = bScore = 1; break;
7426           case ' ':
7427           case '*': return strdup("busy"); // tourney not finished
7428         }
7429         score[w] += wScore;
7430         score[b] += bScore;
7431         games[w]++;
7432         games[b]++;
7433         nr++;
7434     }
7435     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7436     for(w=0; w<nPlayers; w++) {
7437         bScore = -1;
7438         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7439         ranking[w] = b; points[w] = bScore; score[b] = -2;
7440     }
7441     p = malloc(nPlayers*34+1);
7442     for(w=0; w<nPlayers && w<display; w++)
7443         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7444     free(names[0]);
7445     return p;
7446 }
7447
7448 void
7449 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7450 {       // count all piece types
7451         int p, f, r;
7452         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7453         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7454         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7455                 p = board[r][f];
7456                 pCnt[p]++;
7457                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7458                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7459                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7460                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7461                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7462                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7463         }
7464 }
7465
7466 int
7467 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7468 {
7469         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7470         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7471
7472         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7473         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7474         if(myPawns == 2 && nMine == 3) // KPP
7475             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7476         if(myPawns == 1 && nMine == 2) // KP
7477             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7478         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7479             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7480         if(myPawns) return FALSE;
7481         if(pCnt[WhiteRook+side])
7482             return pCnt[BlackRook-side] ||
7483                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7484                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7485                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7486         if(pCnt[WhiteCannon+side]) {
7487             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7488             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7489         }
7490         if(pCnt[WhiteKnight+side])
7491             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7492         return FALSE;
7493 }
7494
7495 int
7496 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7497 {
7498         VariantClass v = gameInfo.variant;
7499
7500         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7501         if(v == VariantShatranj) return TRUE; // always winnable through baring
7502         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7503         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7504
7505         if(v == VariantXiangqi) {
7506                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7507
7508                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7509                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7510                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7511                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7512                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7513                 if(stale) // we have at least one last-rank P plus perhaps C
7514                     return majors // KPKX
7515                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7516                 else // KCA*E*
7517                     return pCnt[WhiteFerz+side] // KCAK
7518                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7519                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7520                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7521
7522         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7523                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7524
7525                 if(nMine == 1) return FALSE; // bare King
7526                 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
7527                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7528                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7529                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7530                 if(pCnt[WhiteKnight+side])
7531                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7532                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7533                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7534                 if(nBishops)
7535                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7536                 if(pCnt[WhiteAlfil+side])
7537                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7538                 if(pCnt[WhiteWazir+side])
7539                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7540         }
7541
7542         return TRUE;
7543 }
7544
7545 int
7546 CompareWithRights (Board b1, Board b2)
7547 {
7548     int rights = 0;
7549     if(!CompareBoards(b1, b2)) return FALSE;
7550     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7551     /* compare castling rights */
7552     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7553            rights++; /* King lost rights, while rook still had them */
7554     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7555         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7556            rights++; /* but at least one rook lost them */
7557     }
7558     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7559            rights++;
7560     if( b1[CASTLING][5] != NoRights ) {
7561         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7562            rights++;
7563     }
7564     return rights == 0;
7565 }
7566
7567 int
7568 Adjudicate (ChessProgramState *cps)
7569 {       // [HGM] some adjudications useful with buggy engines
7570         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7571         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7572         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7573         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7574         int k, count = 0; static int bare = 1;
7575         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7576         Boolean canAdjudicate = !appData.icsActive;
7577
7578         // most tests only when we understand the game, i.e. legality-checking on
7579             if( appData.testLegality )
7580             {   /* [HGM] Some more adjudications for obstinate engines */
7581                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7582                 static int moveCount = 6;
7583                 ChessMove result;
7584                 char *reason = NULL;
7585
7586                 /* Count what is on board. */
7587                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7588
7589                 /* Some material-based adjudications that have to be made before stalemate test */
7590                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7591                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7592                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7593                      if(canAdjudicate && appData.checkMates) {
7594                          if(engineOpponent)
7595                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7596                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7597                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7598                          return 1;
7599                      }
7600                 }
7601
7602                 /* Bare King in Shatranj (loses) or Losers (wins) */
7603                 if( nrW == 1 || nrB == 1) {
7604                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7605                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7606                      if(canAdjudicate && appData.checkMates) {
7607                          if(engineOpponent)
7608                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7609                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7610                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7611                          return 1;
7612                      }
7613                   } else
7614                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7615                   {    /* bare King */
7616                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7617                         if(canAdjudicate && appData.checkMates) {
7618                             /* but only adjudicate if adjudication enabled */
7619                             if(engineOpponent)
7620                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7621                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7622                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7623                             return 1;
7624                         }
7625                   }
7626                 } else bare = 1;
7627
7628
7629             // don't wait for engine to announce game end if we can judge ourselves
7630             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7631               case MT_CHECK:
7632                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7633                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7634                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7635                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7636                             checkCnt++;
7637                         if(checkCnt >= 2) {
7638                             reason = "Xboard adjudication: 3rd check";
7639                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7640                             break;
7641                         }
7642                     }
7643                 }
7644               case MT_NONE:
7645               default:
7646                 break;
7647               case MT_STALEMATE:
7648               case MT_STAINMATE:
7649                 reason = "Xboard adjudication: Stalemate";
7650                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7651                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7652                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7653                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7654                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7655                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7656                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7657                                                                         EP_CHECKMATE : EP_WINS);
7658                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7659                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7660                 }
7661                 break;
7662               case MT_CHECKMATE:
7663                 reason = "Xboard adjudication: Checkmate";
7664                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7665                 break;
7666             }
7667
7668                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7669                     case EP_STALEMATE:
7670                         result = GameIsDrawn; break;
7671                     case EP_CHECKMATE:
7672                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7673                     case EP_WINS:
7674                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7675                     default:
7676                         result = EndOfFile;
7677                 }
7678                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7679                     if(engineOpponent)
7680                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7681                     GameEnds( result, reason, GE_XBOARD );
7682                     return 1;
7683                 }
7684
7685                 /* Next absolutely insufficient mating material. */
7686                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7687                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7688                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7689
7690                      /* always flag draws, for judging claims */
7691                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7692
7693                      if(canAdjudicate && appData.materialDraws) {
7694                          /* but only adjudicate them if adjudication enabled */
7695                          if(engineOpponent) {
7696                            SendToProgram("force\n", engineOpponent); // suppress reply
7697                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7698                          }
7699                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7700                          return 1;
7701                      }
7702                 }
7703
7704                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7705                 if(gameInfo.variant == VariantXiangqi ?
7706                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7707                  : nrW + nrB == 4 &&
7708                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7709                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7710                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7711                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7712                    ) ) {
7713                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7714                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7715                           if(engineOpponent) {
7716                             SendToProgram("force\n", engineOpponent); // suppress reply
7717                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7718                           }
7719                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7720                           return 1;
7721                      }
7722                 } else moveCount = 6;
7723             }
7724
7725         // Repetition draws and 50-move rule can be applied independently of legality testing
7726
7727                 /* Check for rep-draws */
7728                 count = 0;
7729                 for(k = forwardMostMove-2;
7730                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7731                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7732                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7733                     k-=2)
7734                 {   int rights=0;
7735                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7736                         /* compare castling rights */
7737                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7738                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7739                                 rights++; /* King lost rights, while rook still had them */
7740                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7741                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7742                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7743                                    rights++; /* but at least one rook lost them */
7744                         }
7745                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7746                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7747                                 rights++;
7748                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7749                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7750                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7751                                    rights++;
7752                         }
7753                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7754                             && appData.drawRepeats > 1) {
7755                              /* adjudicate after user-specified nr of repeats */
7756                              int result = GameIsDrawn;
7757                              char *details = "XBoard adjudication: repetition draw";
7758                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7759                                 // [HGM] xiangqi: check for forbidden perpetuals
7760                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7761                                 for(m=forwardMostMove; m>k; m-=2) {
7762                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7763                                         ourPerpetual = 0; // the current mover did not always check
7764                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7765                                         hisPerpetual = 0; // the opponent did not always check
7766                                 }
7767                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7768                                                                         ourPerpetual, hisPerpetual);
7769                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7770                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7771                                     details = "Xboard adjudication: perpetual checking";
7772                                 } else
7773                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7774                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7775                                 } else
7776                                 // Now check for perpetual chases
7777                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7778                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7779                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7780                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7781                                         static char resdet[MSG_SIZ];
7782                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7783                                         details = resdet;
7784                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7785                                     } else
7786                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7787                                         break; // Abort repetition-checking loop.
7788                                 }
7789                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7790                              }
7791                              if(engineOpponent) {
7792                                SendToProgram("force\n", engineOpponent); // suppress reply
7793                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7794                              }
7795                              GameEnds( result, details, GE_XBOARD );
7796                              return 1;
7797                         }
7798                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7799                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7800                     }
7801                 }
7802
7803                 /* Now we test for 50-move draws. Determine ply count */
7804                 count = forwardMostMove;
7805                 /* look for last irreversble move */
7806                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7807                     count--;
7808                 /* if we hit starting position, add initial plies */
7809                 if( count == backwardMostMove )
7810                     count -= initialRulePlies;
7811                 count = forwardMostMove - count;
7812                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7813                         // adjust reversible move counter for checks in Xiangqi
7814                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7815                         if(i < backwardMostMove) i = backwardMostMove;
7816                         while(i <= forwardMostMove) {
7817                                 lastCheck = inCheck; // check evasion does not count
7818                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7819                                 if(inCheck || lastCheck) count--; // check does not count
7820                                 i++;
7821                         }
7822                 }
7823                 if( count >= 100)
7824                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7825                          /* this is used to judge if draw claims are legal */
7826                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7827                          if(engineOpponent) {
7828                            SendToProgram("force\n", engineOpponent); // suppress reply
7829                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7830                          }
7831                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7832                          return 1;
7833                 }
7834
7835                 /* if draw offer is pending, treat it as a draw claim
7836                  * when draw condition present, to allow engines a way to
7837                  * claim draws before making their move to avoid a race
7838                  * condition occurring after their move
7839                  */
7840                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7841                          char *p = NULL;
7842                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7843                              p = "Draw claim: 50-move rule";
7844                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7845                              p = "Draw claim: 3-fold repetition";
7846                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7847                              p = "Draw claim: insufficient mating material";
7848                          if( p != NULL && canAdjudicate) {
7849                              if(engineOpponent) {
7850                                SendToProgram("force\n", engineOpponent); // suppress reply
7851                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7852                              }
7853                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7854                              return 1;
7855                          }
7856                 }
7857
7858                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7859                     if(engineOpponent) {
7860                       SendToProgram("force\n", engineOpponent); // suppress reply
7861                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7862                     }
7863                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7864                     return 1;
7865                 }
7866         return 0;
7867 }
7868
7869 char *
7870 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7871 {   // [HGM] book: this routine intercepts moves to simulate book replies
7872     char *bookHit = NULL;
7873
7874     //first determine if the incoming move brings opponent into his book
7875     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7876         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7877     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7878     if(bookHit != NULL && !cps->bookSuspend) {
7879         // make sure opponent is not going to reply after receiving move to book position
7880         SendToProgram("force\n", cps);
7881         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7882     }
7883     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7884     // now arrange restart after book miss
7885     if(bookHit) {
7886         // after a book hit we never send 'go', and the code after the call to this routine
7887         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7888         char buf[MSG_SIZ], *move = bookHit;
7889         if(cps->useSAN) {
7890             int fromX, fromY, toX, toY;
7891             char promoChar;
7892             ChessMove moveType;
7893             move = buf + 30;
7894             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7895                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7896                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7897                                     PosFlags(forwardMostMove),
7898                                     fromY, fromX, toY, toX, promoChar, move);
7899             } else {
7900                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7901                 bookHit = NULL;
7902             }
7903         }
7904         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7905         SendToProgram(buf, cps);
7906         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7907     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7908         SendToProgram("go\n", cps);
7909         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7910     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7911         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7912             SendToProgram("go\n", cps);
7913         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7914     }
7915     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7916 }
7917
7918 char *savedMessage;
7919 ChessProgramState *savedState;
7920 void
7921 DeferredBookMove (void)
7922 {
7923         if(savedState->lastPing != savedState->lastPong)
7924                     ScheduleDelayedEvent(DeferredBookMove, 10);
7925         else
7926         HandleMachineMove(savedMessage, savedState);
7927 }
7928
7929 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7930
7931 void
7932 HandleMachineMove (char *message, ChessProgramState *cps)
7933 {
7934     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7935     char realname[MSG_SIZ];
7936     int fromX, fromY, toX, toY;
7937     ChessMove moveType;
7938     char promoChar;
7939     char *p, *pv=buf1;
7940     int machineWhite;
7941     char *bookHit;
7942
7943     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7944         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7945         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7946             DisplayError(_("Invalid pairing from pairing engine"), 0);
7947             return;
7948         }
7949         pairingReceived = 1;
7950         NextMatchGame();
7951         return; // Skim the pairing messages here.
7952     }
7953
7954     cps->userError = 0;
7955
7956 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7957     /*
7958      * Kludge to ignore BEL characters
7959      */
7960     while (*message == '\007') message++;
7961
7962     /*
7963      * [HGM] engine debug message: ignore lines starting with '#' character
7964      */
7965     if(cps->debug && *message == '#') return;
7966
7967     /*
7968      * Look for book output
7969      */
7970     if (cps == &first && bookRequested) {
7971         if (message[0] == '\t' || message[0] == ' ') {
7972             /* Part of the book output is here; append it */
7973             strcat(bookOutput, message);
7974             strcat(bookOutput, "  \n");
7975             return;
7976         } else if (bookOutput[0] != NULLCHAR) {
7977             /* All of book output has arrived; display it */
7978             char *p = bookOutput;
7979             while (*p != NULLCHAR) {
7980                 if (*p == '\t') *p = ' ';
7981                 p++;
7982             }
7983             DisplayInformation(bookOutput);
7984             bookRequested = FALSE;
7985             /* Fall through to parse the current output */
7986         }
7987     }
7988
7989     /*
7990      * Look for machine move.
7991      */
7992     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7993         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7994     {
7995         /* This method is only useful on engines that support ping */
7996         if (cps->lastPing != cps->lastPong) {
7997           if (gameMode == BeginningOfGame) {
7998             /* Extra move from before last new; ignore */
7999             if (appData.debugMode) {
8000                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8001             }
8002           } else {
8003             if (appData.debugMode) {
8004                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8005                         cps->which, gameMode);
8006             }
8007
8008             SendToProgram("undo\n", cps);
8009           }
8010           return;
8011         }
8012
8013         switch (gameMode) {
8014           case BeginningOfGame:
8015             /* Extra move from before last reset; ignore */
8016             if (appData.debugMode) {
8017                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8018             }
8019             return;
8020
8021           case EndOfGame:
8022           case IcsIdle:
8023           default:
8024             /* Extra move after we tried to stop.  The mode test is
8025                not a reliable way of detecting this problem, but it's
8026                the best we can do on engines that don't support ping.
8027             */
8028             if (appData.debugMode) {
8029                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8030                         cps->which, gameMode);
8031             }
8032             SendToProgram("undo\n", cps);
8033             return;
8034
8035           case MachinePlaysWhite:
8036           case IcsPlayingWhite:
8037             machineWhite = TRUE;
8038             break;
8039
8040           case MachinePlaysBlack:
8041           case IcsPlayingBlack:
8042             machineWhite = FALSE;
8043             break;
8044
8045           case TwoMachinesPlay:
8046             machineWhite = (cps->twoMachinesColor[0] == 'w');
8047             break;
8048         }
8049         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8050             if (appData.debugMode) {
8051                 fprintf(debugFP,
8052                         "Ignoring move out of turn by %s, gameMode %d"
8053                         ", forwardMost %d\n",
8054                         cps->which, gameMode, forwardMostMove);
8055             }
8056             return;
8057         }
8058
8059         if(cps->alphaRank) AlphaRank(machineMove, 4);
8060         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8061                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8062             /* Machine move could not be parsed; ignore it. */
8063           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8064                     machineMove, _(cps->which));
8065             DisplayError(buf1, 0);
8066             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8067                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8068             if (gameMode == TwoMachinesPlay) {
8069               GameEnds(machineWhite ? BlackWins : WhiteWins,
8070                        buf1, GE_XBOARD);
8071             }
8072             return;
8073         }
8074
8075         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8076         /* So we have to redo legality test with true e.p. status here,  */
8077         /* to make sure an illegal e.p. capture does not slip through,   */
8078         /* to cause a forfeit on a justified illegal-move complaint      */
8079         /* of the opponent.                                              */
8080         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8081            ChessMove moveType;
8082            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8083                              fromY, fromX, toY, toX, promoChar);
8084             if(moveType == IllegalMove) {
8085               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8086                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8087                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8088                            buf1, GE_XBOARD);
8089                 return;
8090            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8091            /* [HGM] Kludge to handle engines that send FRC-style castling
8092               when they shouldn't (like TSCP-Gothic) */
8093            switch(moveType) {
8094              case WhiteASideCastleFR:
8095              case BlackASideCastleFR:
8096                toX+=2;
8097                currentMoveString[2]++;
8098                break;
8099              case WhiteHSideCastleFR:
8100              case BlackHSideCastleFR:
8101                toX--;
8102                currentMoveString[2]--;
8103                break;
8104              default: ; // nothing to do, but suppresses warning of pedantic compilers
8105            }
8106         }
8107         hintRequested = FALSE;
8108         lastHint[0] = NULLCHAR;
8109         bookRequested = FALSE;
8110         /* Program may be pondering now */
8111         cps->maybeThinking = TRUE;
8112         if (cps->sendTime == 2) cps->sendTime = 1;
8113         if (cps->offeredDraw) cps->offeredDraw--;
8114
8115         /* [AS] Save move info*/
8116         pvInfoList[ forwardMostMove ].score = programStats.score;
8117         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8118         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8119
8120         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8121
8122         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8123         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8124             int count = 0;
8125
8126             while( count < adjudicateLossPlies ) {
8127                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8128
8129                 if( count & 1 ) {
8130                     score = -score; /* Flip score for winning side */
8131                 }
8132
8133                 if( score > adjudicateLossThreshold ) {
8134                     break;
8135                 }
8136
8137                 count++;
8138             }
8139
8140             if( count >= adjudicateLossPlies ) {
8141                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8142
8143                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8144                     "Xboard adjudication",
8145                     GE_XBOARD );
8146
8147                 return;
8148             }
8149         }
8150
8151         if(Adjudicate(cps)) {
8152             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8153             return; // [HGM] adjudicate: for all automatic game ends
8154         }
8155
8156 #if ZIPPY
8157         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8158             first.initDone) {
8159           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8160                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8161                 SendToICS("draw ");
8162                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8163           }
8164           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8165           ics_user_moved = 1;
8166           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8167                 char buf[3*MSG_SIZ];
8168
8169                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8170                         programStats.score / 100.,
8171                         programStats.depth,
8172                         programStats.time / 100.,
8173                         (unsigned int)programStats.nodes,
8174                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8175                         programStats.movelist);
8176                 SendToICS(buf);
8177 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8178           }
8179         }
8180 #endif
8181
8182         /* [AS] Clear stats for next move */
8183         ClearProgramStats();
8184         thinkOutput[0] = NULLCHAR;
8185         hiddenThinkOutputState = 0;
8186
8187         bookHit = NULL;
8188         if (gameMode == TwoMachinesPlay) {
8189             /* [HGM] relaying draw offers moved to after reception of move */
8190             /* and interpreting offer as claim if it brings draw condition */
8191             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8192                 SendToProgram("draw\n", cps->other);
8193             }
8194             if (cps->other->sendTime) {
8195                 SendTimeRemaining(cps->other,
8196                                   cps->other->twoMachinesColor[0] == 'w');
8197             }
8198             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8199             if (firstMove && !bookHit) {
8200                 firstMove = FALSE;
8201                 if (cps->other->useColors) {
8202                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8203                 }
8204                 SendToProgram("go\n", cps->other);
8205             }
8206             cps->other->maybeThinking = TRUE;
8207         }
8208
8209         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8210
8211         if (!pausing && appData.ringBellAfterMoves) {
8212             RingBell();
8213         }
8214
8215         /*
8216          * Reenable menu items that were disabled while
8217          * machine was thinking
8218          */
8219         if (gameMode != TwoMachinesPlay)
8220             SetUserThinkingEnables();
8221
8222         // [HGM] book: after book hit opponent has received move and is now in force mode
8223         // force the book reply into it, and then fake that it outputted this move by jumping
8224         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8225         if(bookHit) {
8226                 static char bookMove[MSG_SIZ]; // a bit generous?
8227
8228                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8229                 strcat(bookMove, bookHit);
8230                 message = bookMove;
8231                 cps = cps->other;
8232                 programStats.nodes = programStats.depth = programStats.time =
8233                 programStats.score = programStats.got_only_move = 0;
8234                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8235
8236                 if(cps->lastPing != cps->lastPong) {
8237                     savedMessage = message; // args for deferred call
8238                     savedState = cps;
8239                     ScheduleDelayedEvent(DeferredBookMove, 10);
8240                     return;
8241                 }
8242                 goto FakeBookMove;
8243         }
8244
8245         return;
8246     }
8247
8248     /* Set special modes for chess engines.  Later something general
8249      *  could be added here; for now there is just one kludge feature,
8250      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8251      *  when "xboard" is given as an interactive command.
8252      */
8253     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8254         cps->useSigint = FALSE;
8255         cps->useSigterm = FALSE;
8256     }
8257     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8258       ParseFeatures(message+8, cps);
8259       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8260     }
8261
8262     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8263                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8264       int dummy, s=6; char buf[MSG_SIZ];
8265       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8266       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8267       if(startedFromSetupPosition) return;
8268       ParseFEN(boards[0], &dummy, message+s);
8269       DrawPosition(TRUE, boards[0]);
8270       startedFromSetupPosition = TRUE;
8271       return;
8272     }
8273     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8274      * want this, I was asked to put it in, and obliged.
8275      */
8276     if (!strncmp(message, "setboard ", 9)) {
8277         Board initial_position;
8278
8279         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8280
8281         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8282             DisplayError(_("Bad FEN received from engine"), 0);
8283             return ;
8284         } else {
8285            Reset(TRUE, FALSE);
8286            CopyBoard(boards[0], initial_position);
8287            initialRulePlies = FENrulePlies;
8288            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8289            else gameMode = MachinePlaysBlack;
8290            DrawPosition(FALSE, boards[currentMove]);
8291         }
8292         return;
8293     }
8294
8295     /*
8296      * Look for communication commands
8297      */
8298     if (!strncmp(message, "telluser ", 9)) {
8299         if(message[9] == '\\' && message[10] == '\\')
8300             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8301         PlayTellSound();
8302         DisplayNote(message + 9);
8303         return;
8304     }
8305     if (!strncmp(message, "tellusererror ", 14)) {
8306         cps->userError = 1;
8307         if(message[14] == '\\' && message[15] == '\\')
8308             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8309         PlayTellSound();
8310         DisplayError(message + 14, 0);
8311         return;
8312     }
8313     if (!strncmp(message, "tellopponent ", 13)) {
8314       if (appData.icsActive) {
8315         if (loggedOn) {
8316           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8317           SendToICS(buf1);
8318         }
8319       } else {
8320         DisplayNote(message + 13);
8321       }
8322       return;
8323     }
8324     if (!strncmp(message, "tellothers ", 11)) {
8325       if (appData.icsActive) {
8326         if (loggedOn) {
8327           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8328           SendToICS(buf1);
8329         }
8330       }
8331       return;
8332     }
8333     if (!strncmp(message, "tellall ", 8)) {
8334       if (appData.icsActive) {
8335         if (loggedOn) {
8336           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8337           SendToICS(buf1);
8338         }
8339       } else {
8340         DisplayNote(message + 8);
8341       }
8342       return;
8343     }
8344     if (strncmp(message, "warning", 7) == 0) {
8345         /* Undocumented feature, use tellusererror in new code */
8346         DisplayError(message, 0);
8347         return;
8348     }
8349     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8350         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8351         strcat(realname, " query");
8352         AskQuestion(realname, buf2, buf1, cps->pr);
8353         return;
8354     }
8355     /* Commands from the engine directly to ICS.  We don't allow these to be
8356      *  sent until we are logged on. Crafty kibitzes have been known to
8357      *  interfere with the login process.
8358      */
8359     if (loggedOn) {
8360         if (!strncmp(message, "tellics ", 8)) {
8361             SendToICS(message + 8);
8362             SendToICS("\n");
8363             return;
8364         }
8365         if (!strncmp(message, "tellicsnoalias ", 15)) {
8366             SendToICS(ics_prefix);
8367             SendToICS(message + 15);
8368             SendToICS("\n");
8369             return;
8370         }
8371         /* The following are for backward compatibility only */
8372         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8373             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8374             SendToICS(ics_prefix);
8375             SendToICS(message);
8376             SendToICS("\n");
8377             return;
8378         }
8379     }
8380     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8381         return;
8382     }
8383     /*
8384      * If the move is illegal, cancel it and redraw the board.
8385      * Also deal with other error cases.  Matching is rather loose
8386      * here to accommodate engines written before the spec.
8387      */
8388     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8389         strncmp(message, "Error", 5) == 0) {
8390         if (StrStr(message, "name") ||
8391             StrStr(message, "rating") || StrStr(message, "?") ||
8392             StrStr(message, "result") || StrStr(message, "board") ||
8393             StrStr(message, "bk") || StrStr(message, "computer") ||
8394             StrStr(message, "variant") || StrStr(message, "hint") ||
8395             StrStr(message, "random") || StrStr(message, "depth") ||
8396             StrStr(message, "accepted")) {
8397             return;
8398         }
8399         if (StrStr(message, "protover")) {
8400           /* Program is responding to input, so it's apparently done
8401              initializing, and this error message indicates it is
8402              protocol version 1.  So we don't need to wait any longer
8403              for it to initialize and send feature commands. */
8404           FeatureDone(cps, 1);
8405           cps->protocolVersion = 1;
8406           return;
8407         }
8408         cps->maybeThinking = FALSE;
8409
8410         if (StrStr(message, "draw")) {
8411             /* Program doesn't have "draw" command */
8412             cps->sendDrawOffers = 0;
8413             return;
8414         }
8415         if (cps->sendTime != 1 &&
8416             (StrStr(message, "time") || StrStr(message, "otim"))) {
8417           /* Program apparently doesn't have "time" or "otim" command */
8418           cps->sendTime = 0;
8419           return;
8420         }
8421         if (StrStr(message, "analyze")) {
8422             cps->analysisSupport = FALSE;
8423             cps->analyzing = FALSE;
8424 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8425             EditGameEvent(); // [HGM] try to preserve loaded game
8426             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8427             DisplayError(buf2, 0);
8428             return;
8429         }
8430         if (StrStr(message, "(no matching move)st")) {
8431           /* Special kludge for GNU Chess 4 only */
8432           cps->stKludge = TRUE;
8433           SendTimeControl(cps, movesPerSession, timeControl,
8434                           timeIncrement, appData.searchDepth,
8435                           searchTime);
8436           return;
8437         }
8438         if (StrStr(message, "(no matching move)sd")) {
8439           /* Special kludge for GNU Chess 4 only */
8440           cps->sdKludge = TRUE;
8441           SendTimeControl(cps, movesPerSession, timeControl,
8442                           timeIncrement, appData.searchDepth,
8443                           searchTime);
8444           return;
8445         }
8446         if (!StrStr(message, "llegal")) {
8447             return;
8448         }
8449         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8450             gameMode == IcsIdle) return;
8451         if (forwardMostMove <= backwardMostMove) return;
8452         if (pausing) PauseEvent();
8453       if(appData.forceIllegal) {
8454             // [HGM] illegal: machine refused move; force position after move into it
8455           SendToProgram("force\n", cps);
8456           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8457                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8458                 // when black is to move, while there might be nothing on a2 or black
8459                 // might already have the move. So send the board as if white has the move.
8460                 // But first we must change the stm of the engine, as it refused the last move
8461                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8462                 if(WhiteOnMove(forwardMostMove)) {
8463                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8464                     SendBoard(cps, forwardMostMove); // kludgeless board
8465                 } else {
8466                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8467                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8468                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8469                 }
8470           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8471             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8472                  gameMode == TwoMachinesPlay)
8473               SendToProgram("go\n", cps);
8474             return;
8475       } else
8476         if (gameMode == PlayFromGameFile) {
8477             /* Stop reading this game file */
8478             gameMode = EditGame;
8479             ModeHighlight();
8480         }
8481         /* [HGM] illegal-move claim should forfeit game when Xboard */
8482         /* only passes fully legal moves                            */
8483         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8484             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8485                                 "False illegal-move claim", GE_XBOARD );
8486             return; // do not take back move we tested as valid
8487         }
8488         currentMove = forwardMostMove-1;
8489         DisplayMove(currentMove-1); /* before DisplayMoveError */
8490         SwitchClocks(forwardMostMove-1); // [HGM] race
8491         DisplayBothClocks();
8492         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8493                 parseList[currentMove], _(cps->which));
8494         DisplayMoveError(buf1);
8495         DrawPosition(FALSE, boards[currentMove]);
8496
8497         SetUserThinkingEnables();
8498         return;
8499     }
8500     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8501         /* Program has a broken "time" command that
8502            outputs a string not ending in newline.
8503            Don't use it. */
8504         cps->sendTime = 0;
8505     }
8506
8507     /*
8508      * If chess program startup fails, exit with an error message.
8509      * Attempts to recover here are futile. [HGM] Well, we try anyway
8510      */
8511     if ((StrStr(message, "unknown host") != NULL)
8512         || (StrStr(message, "No remote directory") != NULL)
8513         || (StrStr(message, "not found") != NULL)
8514         || (StrStr(message, "No such file") != NULL)
8515         || (StrStr(message, "can't alloc") != NULL)
8516         || (StrStr(message, "Permission denied") != NULL)) {
8517
8518         cps->maybeThinking = FALSE;
8519         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8520                 _(cps->which), cps->program, cps->host, message);
8521         RemoveInputSource(cps->isr);
8522         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8523             cps->isr = NULL;
8524             DestroyChildProcess(cps->pr, 9 ); // just to be sure
8525             cps->pr = NoProc; 
8526             if(cps == &first) {
8527                 appData.noChessProgram = TRUE;
8528                 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8529                 gameMode = BeginningOfGame; ModeHighlight();
8530                 SetNCPMode();
8531             }
8532             if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8533             DisplayMessage("", ""); // erase waiting message
8534             DisplayError(buf1, 0);
8535         }
8536         return;
8537     }
8538
8539     /*
8540      * Look for hint output
8541      */
8542     if (sscanf(message, "Hint: %s", buf1) == 1) {
8543         if (cps == &first && hintRequested) {
8544             hintRequested = FALSE;
8545             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8546                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8547                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8548                                     PosFlags(forwardMostMove),
8549                                     fromY, fromX, toY, toX, promoChar, buf1);
8550                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8551                 DisplayInformation(buf2);
8552             } else {
8553                 /* Hint move could not be parsed!? */
8554               snprintf(buf2, sizeof(buf2),
8555                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8556                         buf1, _(cps->which));
8557                 DisplayError(buf2, 0);
8558             }
8559         } else {
8560           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8561         }
8562         return;
8563     }
8564
8565     /*
8566      * Ignore other messages if game is not in progress
8567      */
8568     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8569         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8570
8571     /*
8572      * look for win, lose, draw, or draw offer
8573      */
8574     if (strncmp(message, "1-0", 3) == 0) {
8575         char *p, *q, *r = "";
8576         p = strchr(message, '{');
8577         if (p) {
8578             q = strchr(p, '}');
8579             if (q) {
8580                 *q = NULLCHAR;
8581                 r = p + 1;
8582             }
8583         }
8584         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8585         return;
8586     } else if (strncmp(message, "0-1", 3) == 0) {
8587         char *p, *q, *r = "";
8588         p = strchr(message, '{');
8589         if (p) {
8590             q = strchr(p, '}');
8591             if (q) {
8592                 *q = NULLCHAR;
8593                 r = p + 1;
8594             }
8595         }
8596         /* Kludge for Arasan 4.1 bug */
8597         if (strcmp(r, "Black resigns") == 0) {
8598             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8599             return;
8600         }
8601         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8602         return;
8603     } else if (strncmp(message, "1/2", 3) == 0) {
8604         char *p, *q, *r = "";
8605         p = strchr(message, '{');
8606         if (p) {
8607             q = strchr(p, '}');
8608             if (q) {
8609                 *q = NULLCHAR;
8610                 r = p + 1;
8611             }
8612         }
8613
8614         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8615         return;
8616
8617     } else if (strncmp(message, "White resign", 12) == 0) {
8618         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8619         return;
8620     } else if (strncmp(message, "Black resign", 12) == 0) {
8621         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8622         return;
8623     } else if (strncmp(message, "White matches", 13) == 0 ||
8624                strncmp(message, "Black matches", 13) == 0   ) {
8625         /* [HGM] ignore GNUShogi noises */
8626         return;
8627     } else if (strncmp(message, "White", 5) == 0 &&
8628                message[5] != '(' &&
8629                StrStr(message, "Black") == NULL) {
8630         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8631         return;
8632     } else if (strncmp(message, "Black", 5) == 0 &&
8633                message[5] != '(') {
8634         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8635         return;
8636     } else if (strcmp(message, "resign") == 0 ||
8637                strcmp(message, "computer resigns") == 0) {
8638         switch (gameMode) {
8639           case MachinePlaysBlack:
8640           case IcsPlayingBlack:
8641             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8642             break;
8643           case MachinePlaysWhite:
8644           case IcsPlayingWhite:
8645             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8646             break;
8647           case TwoMachinesPlay:
8648             if (cps->twoMachinesColor[0] == 'w')
8649               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8650             else
8651               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8652             break;
8653           default:
8654             /* can't happen */
8655             break;
8656         }
8657         return;
8658     } else if (strncmp(message, "opponent mates", 14) == 0) {
8659         switch (gameMode) {
8660           case MachinePlaysBlack:
8661           case IcsPlayingBlack:
8662             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8663             break;
8664           case MachinePlaysWhite:
8665           case IcsPlayingWhite:
8666             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8667             break;
8668           case TwoMachinesPlay:
8669             if (cps->twoMachinesColor[0] == 'w')
8670               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8671             else
8672               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8673             break;
8674           default:
8675             /* can't happen */
8676             break;
8677         }
8678         return;
8679     } else if (strncmp(message, "computer mates", 14) == 0) {
8680         switch (gameMode) {
8681           case MachinePlaysBlack:
8682           case IcsPlayingBlack:
8683             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8684             break;
8685           case MachinePlaysWhite:
8686           case IcsPlayingWhite:
8687             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8688             break;
8689           case TwoMachinesPlay:
8690             if (cps->twoMachinesColor[0] == 'w')
8691               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8692             else
8693               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8694             break;
8695           default:
8696             /* can't happen */
8697             break;
8698         }
8699         return;
8700     } else if (strncmp(message, "checkmate", 9) == 0) {
8701         if (WhiteOnMove(forwardMostMove)) {
8702             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8703         } else {
8704             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8705         }
8706         return;
8707     } else if (strstr(message, "Draw") != NULL ||
8708                strstr(message, "game is a draw") != NULL) {
8709         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8710         return;
8711     } else if (strstr(message, "offer") != NULL &&
8712                strstr(message, "draw") != NULL) {
8713 #if ZIPPY
8714         if (appData.zippyPlay && first.initDone) {
8715             /* Relay offer to ICS */
8716             SendToICS(ics_prefix);
8717             SendToICS("draw\n");
8718         }
8719 #endif
8720         cps->offeredDraw = 2; /* valid until this engine moves twice */
8721         if (gameMode == TwoMachinesPlay) {
8722             if (cps->other->offeredDraw) {
8723                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8724             /* [HGM] in two-machine mode we delay relaying draw offer      */
8725             /* until after we also have move, to see if it is really claim */
8726             }
8727         } else if (gameMode == MachinePlaysWhite ||
8728                    gameMode == MachinePlaysBlack) {
8729           if (userOfferedDraw) {
8730             DisplayInformation(_("Machine accepts your draw offer"));
8731             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8732           } else {
8733             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8734           }
8735         }
8736     }
8737
8738
8739     /*
8740      * Look for thinking output
8741      */
8742     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8743           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8744                                 ) {
8745         int plylev, mvleft, mvtot, curscore, time;
8746         char mvname[MOVE_LEN];
8747         u64 nodes; // [DM]
8748         char plyext;
8749         int ignore = FALSE;
8750         int prefixHint = FALSE;
8751         mvname[0] = NULLCHAR;
8752
8753         switch (gameMode) {
8754           case MachinePlaysBlack:
8755           case IcsPlayingBlack:
8756             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8757             break;
8758           case MachinePlaysWhite:
8759           case IcsPlayingWhite:
8760             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8761             break;
8762           case AnalyzeMode:
8763           case AnalyzeFile:
8764             break;
8765           case IcsObserving: /* [DM] icsEngineAnalyze */
8766             if (!appData.icsEngineAnalyze) ignore = TRUE;
8767             break;
8768           case TwoMachinesPlay:
8769             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8770                 ignore = TRUE;
8771             }
8772             break;
8773           default:
8774             ignore = TRUE;
8775             break;
8776         }
8777
8778         if (!ignore) {
8779             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8780             buf1[0] = NULLCHAR;
8781             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8782                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8783
8784                 if (plyext != ' ' && plyext != '\t') {
8785                     time *= 100;
8786                 }
8787
8788                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8789                 if( cps->scoreIsAbsolute &&
8790                     ( gameMode == MachinePlaysBlack ||
8791                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8792                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8793                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8794                      !WhiteOnMove(currentMove)
8795                     ) )
8796                 {
8797                     curscore = -curscore;
8798                 }
8799
8800                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8801
8802                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8803                         char buf[MSG_SIZ];
8804                         FILE *f;
8805                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8806                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8807                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8808                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8809                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8810                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8811                                 fclose(f);
8812                         } else DisplayError(_("failed writing PV"), 0);
8813                 }
8814
8815                 tempStats.depth = plylev;
8816                 tempStats.nodes = nodes;
8817                 tempStats.time = time;
8818                 tempStats.score = curscore;
8819                 tempStats.got_only_move = 0;
8820
8821                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8822                         int ticklen;
8823
8824                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8825                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8826                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8827                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8828                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8829                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8830                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8831                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8832                 }
8833
8834                 /* Buffer overflow protection */
8835                 if (pv[0] != NULLCHAR) {
8836                     if (strlen(pv) >= sizeof(tempStats.movelist)
8837                         && appData.debugMode) {
8838                         fprintf(debugFP,
8839                                 "PV is too long; using the first %u bytes.\n",
8840                                 (unsigned) sizeof(tempStats.movelist) - 1);
8841                     }
8842
8843                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8844                 } else {
8845                     sprintf(tempStats.movelist, " no PV\n");
8846                 }
8847
8848                 if (tempStats.seen_stat) {
8849                     tempStats.ok_to_send = 1;
8850                 }
8851
8852                 if (strchr(tempStats.movelist, '(') != NULL) {
8853                     tempStats.line_is_book = 1;
8854                     tempStats.nr_moves = 0;
8855                     tempStats.moves_left = 0;
8856                 } else {
8857                     tempStats.line_is_book = 0;
8858                 }
8859
8860                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8861                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8862
8863                 SendProgramStatsToFrontend( cps, &tempStats );
8864
8865                 /*
8866                     [AS] Protect the thinkOutput buffer from overflow... this
8867                     is only useful if buf1 hasn't overflowed first!
8868                 */
8869                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8870                          plylev,
8871                          (gameMode == TwoMachinesPlay ?
8872                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8873                          ((double) curscore) / 100.0,
8874                          prefixHint ? lastHint : "",
8875                          prefixHint ? " " : "" );
8876
8877                 if( buf1[0] != NULLCHAR ) {
8878                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8879
8880                     if( strlen(pv) > max_len ) {
8881                         if( appData.debugMode) {
8882                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8883                         }
8884                         pv[max_len+1] = '\0';
8885                     }
8886
8887                     strcat( thinkOutput, pv);
8888                 }
8889
8890                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8891                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8892                     DisplayMove(currentMove - 1);
8893                 }
8894                 return;
8895
8896             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8897                 /* crafty (9.25+) says "(only move) <move>"
8898                  * if there is only 1 legal move
8899                  */
8900                 sscanf(p, "(only move) %s", buf1);
8901                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8902                 sprintf(programStats.movelist, "%s (only move)", buf1);
8903                 programStats.depth = 1;
8904                 programStats.nr_moves = 1;
8905                 programStats.moves_left = 1;
8906                 programStats.nodes = 1;
8907                 programStats.time = 1;
8908                 programStats.got_only_move = 1;
8909
8910                 /* Not really, but we also use this member to
8911                    mean "line isn't going to change" (Crafty
8912                    isn't searching, so stats won't change) */
8913                 programStats.line_is_book = 1;
8914
8915                 SendProgramStatsToFrontend( cps, &programStats );
8916
8917                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8918                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8919                     DisplayMove(currentMove - 1);
8920                 }
8921                 return;
8922             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8923                               &time, &nodes, &plylev, &mvleft,
8924                               &mvtot, mvname) >= 5) {
8925                 /* The stat01: line is from Crafty (9.29+) in response
8926                    to the "." command */
8927                 programStats.seen_stat = 1;
8928                 cps->maybeThinking = TRUE;
8929
8930                 if (programStats.got_only_move || !appData.periodicUpdates)
8931                   return;
8932
8933                 programStats.depth = plylev;
8934                 programStats.time = time;
8935                 programStats.nodes = nodes;
8936                 programStats.moves_left = mvleft;
8937                 programStats.nr_moves = mvtot;
8938                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8939                 programStats.ok_to_send = 1;
8940                 programStats.movelist[0] = '\0';
8941
8942                 SendProgramStatsToFrontend( cps, &programStats );
8943
8944                 return;
8945
8946             } else if (strncmp(message,"++",2) == 0) {
8947                 /* Crafty 9.29+ outputs this */
8948                 programStats.got_fail = 2;
8949                 return;
8950
8951             } else if (strncmp(message,"--",2) == 0) {
8952                 /* Crafty 9.29+ outputs this */
8953                 programStats.got_fail = 1;
8954                 return;
8955
8956             } else if (thinkOutput[0] != NULLCHAR &&
8957                        strncmp(message, "    ", 4) == 0) {
8958                 unsigned message_len;
8959
8960                 p = message;
8961                 while (*p && *p == ' ') p++;
8962
8963                 message_len = strlen( p );
8964
8965                 /* [AS] Avoid buffer overflow */
8966                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8967                     strcat(thinkOutput, " ");
8968                     strcat(thinkOutput, p);
8969                 }
8970
8971                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8972                     strcat(programStats.movelist, " ");
8973                     strcat(programStats.movelist, p);
8974                 }
8975
8976                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8977                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8978                     DisplayMove(currentMove - 1);
8979                 }
8980                 return;
8981             }
8982         }
8983         else {
8984             buf1[0] = NULLCHAR;
8985
8986             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8987                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8988             {
8989                 ChessProgramStats cpstats;
8990
8991                 if (plyext != ' ' && plyext != '\t') {
8992                     time *= 100;
8993                 }
8994
8995                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8996                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8997                     curscore = -curscore;
8998                 }
8999
9000                 cpstats.depth = plylev;
9001                 cpstats.nodes = nodes;
9002                 cpstats.time = time;
9003                 cpstats.score = curscore;
9004                 cpstats.got_only_move = 0;
9005                 cpstats.movelist[0] = '\0';
9006
9007                 if (buf1[0] != NULLCHAR) {
9008                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9009                 }
9010
9011                 cpstats.ok_to_send = 0;
9012                 cpstats.line_is_book = 0;
9013                 cpstats.nr_moves = 0;
9014                 cpstats.moves_left = 0;
9015
9016                 SendProgramStatsToFrontend( cps, &cpstats );
9017             }
9018         }
9019     }
9020 }
9021
9022
9023 /* Parse a game score from the character string "game", and
9024    record it as the history of the current game.  The game
9025    score is NOT assumed to start from the standard position.
9026    The display is not updated in any way.
9027    */
9028 void
9029 ParseGameHistory (char *game)
9030 {
9031     ChessMove moveType;
9032     int fromX, fromY, toX, toY, boardIndex;
9033     char promoChar;
9034     char *p, *q;
9035     char buf[MSG_SIZ];
9036
9037     if (appData.debugMode)
9038       fprintf(debugFP, "Parsing game history: %s\n", game);
9039
9040     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9041     gameInfo.site = StrSave(appData.icsHost);
9042     gameInfo.date = PGNDate();
9043     gameInfo.round = StrSave("-");
9044
9045     /* Parse out names of players */
9046     while (*game == ' ') game++;
9047     p = buf;
9048     while (*game != ' ') *p++ = *game++;
9049     *p = NULLCHAR;
9050     gameInfo.white = StrSave(buf);
9051     while (*game == ' ') game++;
9052     p = buf;
9053     while (*game != ' ' && *game != '\n') *p++ = *game++;
9054     *p = NULLCHAR;
9055     gameInfo.black = StrSave(buf);
9056
9057     /* Parse moves */
9058     boardIndex = blackPlaysFirst ? 1 : 0;
9059     yynewstr(game);
9060     for (;;) {
9061         yyboardindex = boardIndex;
9062         moveType = (ChessMove) Myylex();
9063         switch (moveType) {
9064           case IllegalMove:             /* maybe suicide chess, etc. */
9065   if (appData.debugMode) {
9066     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9067     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9068     setbuf(debugFP, NULL);
9069   }
9070           case WhitePromotion:
9071           case BlackPromotion:
9072           case WhiteNonPromotion:
9073           case BlackNonPromotion:
9074           case NormalMove:
9075           case WhiteCapturesEnPassant:
9076           case BlackCapturesEnPassant:
9077           case WhiteKingSideCastle:
9078           case WhiteQueenSideCastle:
9079           case BlackKingSideCastle:
9080           case BlackQueenSideCastle:
9081           case WhiteKingSideCastleWild:
9082           case WhiteQueenSideCastleWild:
9083           case BlackKingSideCastleWild:
9084           case BlackQueenSideCastleWild:
9085           /* PUSH Fabien */
9086           case WhiteHSideCastleFR:
9087           case WhiteASideCastleFR:
9088           case BlackHSideCastleFR:
9089           case BlackASideCastleFR:
9090           /* POP Fabien */
9091             fromX = currentMoveString[0] - AAA;
9092             fromY = currentMoveString[1] - ONE;
9093             toX = currentMoveString[2] - AAA;
9094             toY = currentMoveString[3] - ONE;
9095             promoChar = currentMoveString[4];
9096             break;
9097           case WhiteDrop:
9098           case BlackDrop:
9099             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9100             fromX = moveType == WhiteDrop ?
9101               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9102             (int) CharToPiece(ToLower(currentMoveString[0]));
9103             fromY = DROP_RANK;
9104             toX = currentMoveString[2] - AAA;
9105             toY = currentMoveString[3] - ONE;
9106             promoChar = NULLCHAR;
9107             break;
9108           case AmbiguousMove:
9109             /* bug? */
9110             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9111   if (appData.debugMode) {
9112     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9113     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9114     setbuf(debugFP, NULL);
9115   }
9116             DisplayError(buf, 0);
9117             return;
9118           case ImpossibleMove:
9119             /* bug? */
9120             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9121   if (appData.debugMode) {
9122     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9123     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9124     setbuf(debugFP, NULL);
9125   }
9126             DisplayError(buf, 0);
9127             return;
9128           case EndOfFile:
9129             if (boardIndex < backwardMostMove) {
9130                 /* Oops, gap.  How did that happen? */
9131                 DisplayError(_("Gap in move list"), 0);
9132                 return;
9133             }
9134             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9135             if (boardIndex > forwardMostMove) {
9136                 forwardMostMove = boardIndex;
9137             }
9138             return;
9139           case ElapsedTime:
9140             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9141                 strcat(parseList[boardIndex-1], " ");
9142                 strcat(parseList[boardIndex-1], yy_text);
9143             }
9144             continue;
9145           case Comment:
9146           case PGNTag:
9147           case NAG:
9148           default:
9149             /* ignore */
9150             continue;
9151           case WhiteWins:
9152           case BlackWins:
9153           case GameIsDrawn:
9154           case GameUnfinished:
9155             if (gameMode == IcsExamining) {
9156                 if (boardIndex < backwardMostMove) {
9157                     /* Oops, gap.  How did that happen? */
9158                     return;
9159                 }
9160                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9161                 return;
9162             }
9163             gameInfo.result = moveType;
9164             p = strchr(yy_text, '{');
9165             if (p == NULL) p = strchr(yy_text, '(');
9166             if (p == NULL) {
9167                 p = yy_text;
9168                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9169             } else {
9170                 q = strchr(p, *p == '{' ? '}' : ')');
9171                 if (q != NULL) *q = NULLCHAR;
9172                 p++;
9173             }
9174             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9175             gameInfo.resultDetails = StrSave(p);
9176             continue;
9177         }
9178         if (boardIndex >= forwardMostMove &&
9179             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9180             backwardMostMove = blackPlaysFirst ? 1 : 0;
9181             return;
9182         }
9183         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9184                                  fromY, fromX, toY, toX, promoChar,
9185                                  parseList[boardIndex]);
9186         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9187         /* currentMoveString is set as a side-effect of yylex */
9188         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9189         strcat(moveList[boardIndex], "\n");
9190         boardIndex++;
9191         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9192         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9193           case MT_NONE:
9194           case MT_STALEMATE:
9195           default:
9196             break;
9197           case MT_CHECK:
9198             if(gameInfo.variant != VariantShogi)
9199                 strcat(parseList[boardIndex - 1], "+");
9200             break;
9201           case MT_CHECKMATE:
9202           case MT_STAINMATE:
9203             strcat(parseList[boardIndex - 1], "#");
9204             break;
9205         }
9206     }
9207 }
9208
9209
9210 /* Apply a move to the given board  */
9211 void
9212 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9213 {
9214   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9215   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9216
9217     /* [HGM] compute & store e.p. status and castling rights for new position */
9218     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9219
9220       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9221       oldEP = (signed char)board[EP_STATUS];
9222       board[EP_STATUS] = EP_NONE;
9223
9224   if (fromY == DROP_RANK) {
9225         /* must be first */
9226         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9227             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9228             return;
9229         }
9230         piece = board[toY][toX] = (ChessSquare) fromX;
9231   } else {
9232       int i;
9233
9234       if( board[toY][toX] != EmptySquare )
9235            board[EP_STATUS] = EP_CAPTURE;
9236
9237       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9238            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9239                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9240       } else
9241       if( board[fromY][fromX] == WhitePawn ) {
9242            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9243                board[EP_STATUS] = EP_PAWN_MOVE;
9244            if( toY-fromY==2) {
9245                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9246                         gameInfo.variant != VariantBerolina || toX < fromX)
9247                       board[EP_STATUS] = toX | berolina;
9248                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9249                         gameInfo.variant != VariantBerolina || toX > fromX)
9250                       board[EP_STATUS] = toX;
9251            }
9252       } else
9253       if( board[fromY][fromX] == BlackPawn ) {
9254            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9255                board[EP_STATUS] = EP_PAWN_MOVE;
9256            if( toY-fromY== -2) {
9257                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9258                         gameInfo.variant != VariantBerolina || toX < fromX)
9259                       board[EP_STATUS] = toX | berolina;
9260                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9261                         gameInfo.variant != VariantBerolina || toX > fromX)
9262                       board[EP_STATUS] = toX;
9263            }
9264        }
9265
9266        for(i=0; i<nrCastlingRights; i++) {
9267            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9268               board[CASTLING][i] == toX   && castlingRank[i] == toY
9269              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9270        }
9271
9272      if (fromX == toX && fromY == toY) return;
9273
9274      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9275      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9276      if(gameInfo.variant == VariantKnightmate)
9277          king += (int) WhiteUnicorn - (int) WhiteKing;
9278
9279     /* Code added by Tord: */
9280     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9281     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9282         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9283       board[fromY][fromX] = EmptySquare;
9284       board[toY][toX] = EmptySquare;
9285       if((toX > fromX) != (piece == WhiteRook)) {
9286         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9287       } else {
9288         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9289       }
9290     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9291                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9292       board[fromY][fromX] = EmptySquare;
9293       board[toY][toX] = EmptySquare;
9294       if((toX > fromX) != (piece == BlackRook)) {
9295         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9296       } else {
9297         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9298       }
9299     /* End of code added by Tord */
9300
9301     } else if (board[fromY][fromX] == king
9302         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9303         && toY == fromY && toX > fromX+1) {
9304         board[fromY][fromX] = EmptySquare;
9305         board[toY][toX] = king;
9306         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9307         board[fromY][BOARD_RGHT-1] = EmptySquare;
9308     } else if (board[fromY][fromX] == king
9309         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9310                && toY == fromY && toX < fromX-1) {
9311         board[fromY][fromX] = EmptySquare;
9312         board[toY][toX] = king;
9313         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9314         board[fromY][BOARD_LEFT] = EmptySquare;
9315     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9316                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9317                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9318                ) {
9319         /* white pawn promotion */
9320         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9321         if(gameInfo.variant==VariantBughouse ||
9322            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9323             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9324         board[fromY][fromX] = EmptySquare;
9325     } else if ((fromY >= BOARD_HEIGHT>>1)
9326                && (toX != fromX)
9327                && gameInfo.variant != VariantXiangqi
9328                && gameInfo.variant != VariantBerolina
9329                && (board[fromY][fromX] == WhitePawn)
9330                && (board[toY][toX] == EmptySquare)) {
9331         board[fromY][fromX] = EmptySquare;
9332         board[toY][toX] = WhitePawn;
9333         captured = board[toY - 1][toX];
9334         board[toY - 1][toX] = EmptySquare;
9335     } else if ((fromY == BOARD_HEIGHT-4)
9336                && (toX == fromX)
9337                && gameInfo.variant == VariantBerolina
9338                && (board[fromY][fromX] == WhitePawn)
9339                && (board[toY][toX] == EmptySquare)) {
9340         board[fromY][fromX] = EmptySquare;
9341         board[toY][toX] = WhitePawn;
9342         if(oldEP & EP_BEROLIN_A) {
9343                 captured = board[fromY][fromX-1];
9344                 board[fromY][fromX-1] = EmptySquare;
9345         }else{  captured = board[fromY][fromX+1];
9346                 board[fromY][fromX+1] = EmptySquare;
9347         }
9348     } else if (board[fromY][fromX] == king
9349         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9350                && toY == fromY && toX > fromX+1) {
9351         board[fromY][fromX] = EmptySquare;
9352         board[toY][toX] = king;
9353         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9354         board[fromY][BOARD_RGHT-1] = EmptySquare;
9355     } else if (board[fromY][fromX] == king
9356         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9357                && toY == fromY && toX < fromX-1) {
9358         board[fromY][fromX] = EmptySquare;
9359         board[toY][toX] = king;
9360         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9361         board[fromY][BOARD_LEFT] = EmptySquare;
9362     } else if (fromY == 7 && fromX == 3
9363                && board[fromY][fromX] == BlackKing
9364                && toY == 7 && toX == 5) {
9365         board[fromY][fromX] = EmptySquare;
9366         board[toY][toX] = BlackKing;
9367         board[fromY][7] = EmptySquare;
9368         board[toY][4] = BlackRook;
9369     } else if (fromY == 7 && fromX == 3
9370                && board[fromY][fromX] == BlackKing
9371                && toY == 7 && toX == 1) {
9372         board[fromY][fromX] = EmptySquare;
9373         board[toY][toX] = BlackKing;
9374         board[fromY][0] = EmptySquare;
9375         board[toY][2] = BlackRook;
9376     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9377                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9378                && toY < promoRank && promoChar
9379                ) {
9380         /* black pawn promotion */
9381         board[toY][toX] = CharToPiece(ToLower(promoChar));
9382         if(gameInfo.variant==VariantBughouse ||
9383            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9384             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9385         board[fromY][fromX] = EmptySquare;
9386     } else if ((fromY < BOARD_HEIGHT>>1)
9387                && (toX != fromX)
9388                && gameInfo.variant != VariantXiangqi
9389                && gameInfo.variant != VariantBerolina
9390                && (board[fromY][fromX] == BlackPawn)
9391                && (board[toY][toX] == EmptySquare)) {
9392         board[fromY][fromX] = EmptySquare;
9393         board[toY][toX] = BlackPawn;
9394         captured = board[toY + 1][toX];
9395         board[toY + 1][toX] = EmptySquare;
9396     } else if ((fromY == 3)
9397                && (toX == fromX)
9398                && gameInfo.variant == VariantBerolina
9399                && (board[fromY][fromX] == BlackPawn)
9400                && (board[toY][toX] == EmptySquare)) {
9401         board[fromY][fromX] = EmptySquare;
9402         board[toY][toX] = BlackPawn;
9403         if(oldEP & EP_BEROLIN_A) {
9404                 captured = board[fromY][fromX-1];
9405                 board[fromY][fromX-1] = EmptySquare;
9406         }else{  captured = board[fromY][fromX+1];
9407                 board[fromY][fromX+1] = EmptySquare;
9408         }
9409     } else {
9410         board[toY][toX] = board[fromY][fromX];
9411         board[fromY][fromX] = EmptySquare;
9412     }
9413   }
9414
9415     if (gameInfo.holdingsWidth != 0) {
9416
9417       /* !!A lot more code needs to be written to support holdings  */
9418       /* [HGM] OK, so I have written it. Holdings are stored in the */
9419       /* penultimate board files, so they are automaticlly stored   */
9420       /* in the game history.                                       */
9421       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9422                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9423         /* Delete from holdings, by decreasing count */
9424         /* and erasing image if necessary            */
9425         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9426         if(p < (int) BlackPawn) { /* white drop */
9427              p -= (int)WhitePawn;
9428                  p = PieceToNumber((ChessSquare)p);
9429              if(p >= gameInfo.holdingsSize) p = 0;
9430              if(--board[p][BOARD_WIDTH-2] <= 0)
9431                   board[p][BOARD_WIDTH-1] = EmptySquare;
9432              if((int)board[p][BOARD_WIDTH-2] < 0)
9433                         board[p][BOARD_WIDTH-2] = 0;
9434         } else {                  /* black drop */
9435              p -= (int)BlackPawn;
9436                  p = PieceToNumber((ChessSquare)p);
9437              if(p >= gameInfo.holdingsSize) p = 0;
9438              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9439                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9440              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9441                         board[BOARD_HEIGHT-1-p][1] = 0;
9442         }
9443       }
9444       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9445           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9446         /* [HGM] holdings: Add to holdings, if holdings exist */
9447         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9448                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9449                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9450         }
9451         p = (int) captured;
9452         if (p >= (int) BlackPawn) {
9453           p -= (int)BlackPawn;
9454           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9455                   /* in Shogi restore piece to its original  first */
9456                   captured = (ChessSquare) (DEMOTED captured);
9457                   p = DEMOTED p;
9458           }
9459           p = PieceToNumber((ChessSquare)p);
9460           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9461           board[p][BOARD_WIDTH-2]++;
9462           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9463         } else {
9464           p -= (int)WhitePawn;
9465           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9466                   captured = (ChessSquare) (DEMOTED captured);
9467                   p = DEMOTED p;
9468           }
9469           p = PieceToNumber((ChessSquare)p);
9470           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9471           board[BOARD_HEIGHT-1-p][1]++;
9472           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9473         }
9474       }
9475     } else if (gameInfo.variant == VariantAtomic) {
9476       if (captured != EmptySquare) {
9477         int y, x;
9478         for (y = toY-1; y <= toY+1; y++) {
9479           for (x = toX-1; x <= toX+1; x++) {
9480             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9481                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9482               board[y][x] = EmptySquare;
9483             }
9484           }
9485         }
9486         board[toY][toX] = EmptySquare;
9487       }
9488     }
9489     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9490         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9491     } else
9492     if(promoChar == '+') {
9493         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9494         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9495     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9496         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9497         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9498            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9499         board[toY][toX] = newPiece;
9500     }
9501     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9502                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9503         // [HGM] superchess: take promotion piece out of holdings
9504         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9505         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9506             if(!--board[k][BOARD_WIDTH-2])
9507                 board[k][BOARD_WIDTH-1] = EmptySquare;
9508         } else {
9509             if(!--board[BOARD_HEIGHT-1-k][1])
9510                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9511         }
9512     }
9513
9514 }
9515
9516 /* Updates forwardMostMove */
9517 void
9518 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9519 {
9520 //    forwardMostMove++; // [HGM] bare: moved downstream
9521
9522     (void) CoordsToAlgebraic(boards[forwardMostMove],
9523                              PosFlags(forwardMostMove),
9524                              fromY, fromX, toY, toX, promoChar,
9525                              parseList[forwardMostMove]);
9526
9527     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9528         int timeLeft; static int lastLoadFlag=0; int king, piece;
9529         piece = boards[forwardMostMove][fromY][fromX];
9530         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9531         if(gameInfo.variant == VariantKnightmate)
9532             king += (int) WhiteUnicorn - (int) WhiteKing;
9533         if(forwardMostMove == 0) {
9534             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9535                 fprintf(serverMoves, "%s;", UserName());
9536             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9537                 fprintf(serverMoves, "%s;", second.tidy);
9538             fprintf(serverMoves, "%s;", first.tidy);
9539             if(gameMode == MachinePlaysWhite)
9540                 fprintf(serverMoves, "%s;", UserName());
9541             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9542                 fprintf(serverMoves, "%s;", second.tidy);
9543         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9544         lastLoadFlag = loadFlag;
9545         // print base move
9546         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9547         // print castling suffix
9548         if( toY == fromY && piece == king ) {
9549             if(toX-fromX > 1)
9550                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9551             if(fromX-toX >1)
9552                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9553         }
9554         // e.p. suffix
9555         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9556              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9557              boards[forwardMostMove][toY][toX] == EmptySquare
9558              && fromX != toX && fromY != toY)
9559                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9560         // promotion suffix
9561         if(promoChar != NULLCHAR)
9562                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9563         if(!loadFlag) {
9564                 char buf[MOVE_LEN*2], *p; int len;
9565             fprintf(serverMoves, "/%d/%d",
9566                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9567             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9568             else                      timeLeft = blackTimeRemaining/1000;
9569             fprintf(serverMoves, "/%d", timeLeft);
9570                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9571                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9572                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9573             fprintf(serverMoves, "/%s", buf);
9574         }
9575         fflush(serverMoves);
9576     }
9577
9578     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9579         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9580       return;
9581     }
9582     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9583     if (commentList[forwardMostMove+1] != NULL) {
9584         free(commentList[forwardMostMove+1]);
9585         commentList[forwardMostMove+1] = NULL;
9586     }
9587     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9588     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9589     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9590     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9591     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9592     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9593     adjustedClock = FALSE;
9594     gameInfo.result = GameUnfinished;
9595     if (gameInfo.resultDetails != NULL) {
9596         free(gameInfo.resultDetails);
9597         gameInfo.resultDetails = NULL;
9598     }
9599     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9600                               moveList[forwardMostMove - 1]);
9601     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9602       case MT_NONE:
9603       case MT_STALEMATE:
9604       default:
9605         break;
9606       case MT_CHECK:
9607         if(gameInfo.variant != VariantShogi)
9608             strcat(parseList[forwardMostMove - 1], "+");
9609         break;
9610       case MT_CHECKMATE:
9611       case MT_STAINMATE:
9612         strcat(parseList[forwardMostMove - 1], "#");
9613         break;
9614     }
9615
9616 }
9617
9618 /* Updates currentMove if not pausing */
9619 void
9620 ShowMove (int fromX, int fromY, int toX, int toY)
9621 {
9622     int instant = (gameMode == PlayFromGameFile) ?
9623         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9624     if(appData.noGUI) return;
9625     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9626         if (!instant) {
9627             if (forwardMostMove == currentMove + 1) {
9628                 AnimateMove(boards[forwardMostMove - 1],
9629                             fromX, fromY, toX, toY);
9630             }
9631             if (appData.highlightLastMove) {
9632                 SetHighlights(fromX, fromY, toX, toY);
9633             }
9634         }
9635         currentMove = forwardMostMove;
9636     }
9637
9638     if (instant) return;
9639
9640     DisplayMove(currentMove - 1);
9641     DrawPosition(FALSE, boards[currentMove]);
9642     DisplayBothClocks();
9643     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9644 }
9645
9646 void
9647 SendEgtPath (ChessProgramState *cps)
9648 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9649         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9650
9651         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9652
9653         while(*p) {
9654             char c, *q = name+1, *r, *s;
9655
9656             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9657             while(*p && *p != ',') *q++ = *p++;
9658             *q++ = ':'; *q = 0;
9659             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9660                 strcmp(name, ",nalimov:") == 0 ) {
9661                 // take nalimov path from the menu-changeable option first, if it is defined
9662               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9663                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9664             } else
9665             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9666                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9667                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9668                 s = r = StrStr(s, ":") + 1; // beginning of path info
9669                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9670                 c = *r; *r = 0;             // temporarily null-terminate path info
9671                     *--q = 0;               // strip of trailig ':' from name
9672                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9673                 *r = c;
9674                 SendToProgram(buf,cps);     // send egtbpath command for this format
9675             }
9676             if(*p == ',') p++; // read away comma to position for next format name
9677         }
9678 }
9679
9680 void
9681 InitChessProgram (ChessProgramState *cps, int setup)
9682 /* setup needed to setup FRC opening position */
9683 {
9684     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9685     if (appData.noChessProgram) return;
9686     hintRequested = FALSE;
9687     bookRequested = FALSE;
9688
9689     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9690     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9691     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9692     if(cps->memSize) { /* [HGM] memory */
9693       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9694         SendToProgram(buf, cps);
9695     }
9696     SendEgtPath(cps); /* [HGM] EGT */
9697     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9698       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9699         SendToProgram(buf, cps);
9700     }
9701
9702     SendToProgram(cps->initString, cps);
9703     if (gameInfo.variant != VariantNormal &&
9704         gameInfo.variant != VariantLoadable
9705         /* [HGM] also send variant if board size non-standard */
9706         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9707                                             ) {
9708       char *v = VariantName(gameInfo.variant);
9709       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9710         /* [HGM] in protocol 1 we have to assume all variants valid */
9711         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9712         DisplayFatalError(buf, 0, 1);
9713         return;
9714       }
9715
9716       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9717       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9718       if( gameInfo.variant == VariantXiangqi )
9719            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9720       if( gameInfo.variant == VariantShogi )
9721            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9722       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9723            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9724       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9725           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9726            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9727       if( gameInfo.variant == VariantCourier )
9728            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9729       if( gameInfo.variant == VariantSuper )
9730            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9731       if( gameInfo.variant == VariantGreat )
9732            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9733       if( gameInfo.variant == VariantSChess )
9734            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9735       if( gameInfo.variant == VariantGrand )
9736            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9737
9738       if(overruled) {
9739         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9740                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9741            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9742            if(StrStr(cps->variants, b) == NULL) {
9743                // specific sized variant not known, check if general sizing allowed
9744                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9745                    if(StrStr(cps->variants, "boardsize") == NULL) {
9746                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9747                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9748                        DisplayFatalError(buf, 0, 1);
9749                        return;
9750                    }
9751                    /* [HGM] here we really should compare with the maximum supported board size */
9752                }
9753            }
9754       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9755       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9756       SendToProgram(buf, cps);
9757     }
9758     currentlyInitializedVariant = gameInfo.variant;
9759
9760     /* [HGM] send opening position in FRC to first engine */
9761     if(setup) {
9762           SendToProgram("force\n", cps);
9763           SendBoard(cps, 0);
9764           /* engine is now in force mode! Set flag to wake it up after first move. */
9765           setboardSpoiledMachineBlack = 1;
9766     }
9767
9768     if (cps->sendICS) {
9769       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9770       SendToProgram(buf, cps);
9771     }
9772     cps->maybeThinking = FALSE;
9773     cps->offeredDraw = 0;
9774     if (!appData.icsActive) {
9775         SendTimeControl(cps, movesPerSession, timeControl,
9776                         timeIncrement, appData.searchDepth,
9777                         searchTime);
9778     }
9779     if (appData.showThinking
9780         // [HGM] thinking: four options require thinking output to be sent
9781         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9782                                 ) {
9783         SendToProgram("post\n", cps);
9784     }
9785     SendToProgram("hard\n", cps);
9786     if (!appData.ponderNextMove) {
9787         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9788            it without being sure what state we are in first.  "hard"
9789            is not a toggle, so that one is OK.
9790          */
9791         SendToProgram("easy\n", cps);
9792     }
9793     if (cps->usePing) {
9794       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9795       SendToProgram(buf, cps);
9796     }
9797     cps->initDone = TRUE;
9798     ClearEngineOutputPane(cps == &second);
9799 }
9800
9801
9802 void
9803 StartChessProgram (ChessProgramState *cps)
9804 {
9805     char buf[MSG_SIZ];
9806     int err;
9807
9808     if (appData.noChessProgram) return;
9809     cps->initDone = FALSE;
9810
9811     if (strcmp(cps->host, "localhost") == 0) {
9812         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9813     } else if (*appData.remoteShell == NULLCHAR) {
9814         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9815     } else {
9816         if (*appData.remoteUser == NULLCHAR) {
9817           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9818                     cps->program);
9819         } else {
9820           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9821                     cps->host, appData.remoteUser, cps->program);
9822         }
9823         err = StartChildProcess(buf, "", &cps->pr);
9824     }
9825
9826     if (err != 0) {
9827       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9828         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9829         if(cps != &first) return;
9830         appData.noChessProgram = TRUE;
9831         ThawUI();
9832         SetNCPMode();
9833 //      DisplayFatalError(buf, err, 1);
9834 //      cps->pr = NoProc;
9835 //      cps->isr = NULL;
9836         return;
9837     }
9838
9839     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9840     if (cps->protocolVersion > 1) {
9841       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9842       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9843       cps->comboCnt = 0;  //                and values of combo boxes
9844       SendToProgram(buf, cps);
9845     } else {
9846       SendToProgram("xboard\n", cps);
9847     }
9848 }
9849
9850 void
9851 TwoMachinesEventIfReady P((void))
9852 {
9853   static int curMess = 0;
9854   if (first.lastPing != first.lastPong) {
9855     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9856     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9857     return;
9858   }
9859   if (second.lastPing != second.lastPong) {
9860     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9861     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9862     return;
9863   }
9864   DisplayMessage("", ""); curMess = 0;
9865   ThawUI();
9866   TwoMachinesEvent();
9867 }
9868
9869 char *
9870 MakeName (char *template)
9871 {
9872     time_t clock;
9873     struct tm *tm;
9874     static char buf[MSG_SIZ];
9875     char *p = buf;
9876     int i;
9877
9878     clock = time((time_t *)NULL);
9879     tm = localtime(&clock);
9880
9881     while(*p++ = *template++) if(p[-1] == '%') {
9882         switch(*template++) {
9883           case 0:   *p = 0; return buf;
9884           case 'Y': i = tm->tm_year+1900; break;
9885           case 'y': i = tm->tm_year-100; break;
9886           case 'M': i = tm->tm_mon+1; break;
9887           case 'd': i = tm->tm_mday; break;
9888           case 'h': i = tm->tm_hour; break;
9889           case 'm': i = tm->tm_min; break;
9890           case 's': i = tm->tm_sec; break;
9891           default:  i = 0;
9892         }
9893         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9894     }
9895     return buf;
9896 }
9897
9898 int
9899 CountPlayers (char *p)
9900 {
9901     int n = 0;
9902     while(p = strchr(p, '\n')) p++, n++; // count participants
9903     return n;
9904 }
9905
9906 FILE *
9907 WriteTourneyFile (char *results, FILE *f)
9908 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9909     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9910     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9911         // create a file with tournament description
9912         fprintf(f, "-participants {%s}\n", appData.participants);
9913         fprintf(f, "-seedBase %d\n", appData.seedBase);
9914         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9915         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9916         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9917         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9918         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9919         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9920         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9921         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9922         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9923         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9924         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9925         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9926         if(searchTime > 0)
9927                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9928         else {
9929                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9930                 fprintf(f, "-tc %s\n", appData.timeControl);
9931                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9932         }
9933         fprintf(f, "-results \"%s\"\n", results);
9934     }
9935     return f;
9936 }
9937
9938 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9939
9940 void
9941 Substitute (char *participants, int expunge)
9942 {
9943     int i, changed, changes=0, nPlayers=0;
9944     char *p, *q, *r, buf[MSG_SIZ];
9945     if(participants == NULL) return;
9946     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9947     r = p = participants; q = appData.participants;
9948     while(*p && *p == *q) {
9949         if(*p == '\n') r = p+1, nPlayers++;
9950         p++; q++;
9951     }
9952     if(*p) { // difference
9953         while(*p && *p++ != '\n');
9954         while(*q && *q++ != '\n');
9955       changed = nPlayers;
9956         changes = 1 + (strcmp(p, q) != 0);
9957     }
9958     if(changes == 1) { // a single engine mnemonic was changed
9959         q = r; while(*q) nPlayers += (*q++ == '\n');
9960         p = buf; while(*r && (*p = *r++) != '\n') p++;
9961         *p = NULLCHAR;
9962         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9963         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9964         if(mnemonic[i]) { // The substitute is valid
9965             FILE *f;
9966             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9967                 flock(fileno(f), LOCK_EX);
9968                 ParseArgsFromFile(f);
9969                 fseek(f, 0, SEEK_SET);
9970                 FREE(appData.participants); appData.participants = participants;
9971                 if(expunge) { // erase results of replaced engine
9972                     int len = strlen(appData.results), w, b, dummy;
9973                     for(i=0; i<len; i++) {
9974                         Pairing(i, nPlayers, &w, &b, &dummy);
9975                         if((w == changed || b == changed) && appData.results[i] == '*') {
9976                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9977                             fclose(f);
9978                             return;
9979                         }
9980                     }
9981                     for(i=0; i<len; i++) {
9982                         Pairing(i, nPlayers, &w, &b, &dummy);
9983                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9984                     }
9985                 }
9986                 WriteTourneyFile(appData.results, f);
9987                 fclose(f); // release lock
9988                 return;
9989             }
9990         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9991     }
9992     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9993     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9994     free(participants);
9995     return;
9996 }
9997
9998 int
9999 CreateTourney (char *name)
10000 {
10001         FILE *f;
10002         if(matchMode && strcmp(name, appData.tourneyFile)) {
10003              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10004         }
10005         if(name[0] == NULLCHAR) {
10006             if(appData.participants[0])
10007                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10008             return 0;
10009         }
10010         f = fopen(name, "r");
10011         if(f) { // file exists
10012             ASSIGN(appData.tourneyFile, name);
10013             ParseArgsFromFile(f); // parse it
10014         } else {
10015             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10016             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10017                 DisplayError(_("Not enough participants"), 0);
10018                 return 0;
10019             }
10020             ASSIGN(appData.tourneyFile, name);
10021             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10022             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10023         }
10024         fclose(f);
10025         appData.noChessProgram = FALSE;
10026         appData.clockMode = TRUE;
10027         SetGNUMode();
10028         return 1;
10029 }
10030
10031 int
10032 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10033 {
10034     char buf[MSG_SIZ], *p, *q;
10035     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10036     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10037     skip = !all && group[0]; // if group requested, we start in skip mode
10038     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10039         p = names; q = buf; header = 0;
10040         while(*p && *p != '\n') *q++ = *p++;
10041         *q = 0;
10042         if(*p == '\n') p++;
10043         if(buf[0] == '#') {
10044             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10045             depth++; // we must be entering a new group
10046             if(all) continue; // suppress printing group headers when complete list requested
10047             header = 1;
10048             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10049         }
10050         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10051         if(engineList[i]) free(engineList[i]);
10052         engineList[i] = strdup(buf);
10053         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10054         if(engineMnemonic[i]) free(engineMnemonic[i]);
10055         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10056             strcat(buf, " (");
10057             sscanf(q + 8, "%s", buf + strlen(buf));
10058             strcat(buf, ")");
10059         }
10060         engineMnemonic[i] = strdup(buf);
10061         i++;
10062     }
10063     engineList[i] = engineMnemonic[i] = NULL;
10064     return i;
10065 }
10066
10067 // following implemented as macro to avoid type limitations
10068 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10069
10070 void
10071 SwapEngines (int n)
10072 {   // swap settings for first engine and other engine (so far only some selected options)
10073     int h;
10074     char *p;
10075     if(n == 0) return;
10076     SWAP(directory, p)
10077     SWAP(chessProgram, p)
10078     SWAP(isUCI, h)
10079     SWAP(hasOwnBookUCI, h)
10080     SWAP(protocolVersion, h)
10081     SWAP(reuse, h)
10082     SWAP(scoreIsAbsolute, h)
10083     SWAP(timeOdds, h)
10084     SWAP(logo, p)
10085     SWAP(pgnName, p)
10086     SWAP(pvSAN, h)
10087     SWAP(engOptions, p)
10088     SWAP(engInitString, p)
10089     SWAP(computerString, p)
10090     SWAP(features, p)
10091     SWAP(fenOverride, p)
10092     SWAP(NPS, h)
10093     SWAP(accumulateTC, h)
10094     SWAP(host, p)
10095 }
10096
10097 int
10098 SetPlayer (int player, char *p)
10099 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10100     int i;
10101     char buf[MSG_SIZ], *engineName;
10102     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10103     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10104     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10105     if(mnemonic[i]) {
10106         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10107         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10108         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10109         ParseArgsFromString(buf);
10110     }
10111     free(engineName);
10112     return i;
10113 }
10114
10115 char *recentEngines;
10116
10117 void
10118 RecentEngineEvent (int nr)
10119 {
10120     int n;
10121 //    SwapEngines(1); // bump first to second
10122 //    ReplaceEngine(&second, 1); // and load it there
10123     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10124     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10125     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10126         ReplaceEngine(&first, 0);
10127         FloatToFront(&appData.recentEngineList, command[n]);
10128     }
10129 }
10130
10131 int
10132 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10133 {   // determine players from game number
10134     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10135
10136     if(appData.tourneyType == 0) {
10137         roundsPerCycle = (nPlayers - 1) | 1;
10138         pairingsPerRound = nPlayers / 2;
10139     } else if(appData.tourneyType > 0) {
10140         roundsPerCycle = nPlayers - appData.tourneyType;
10141         pairingsPerRound = appData.tourneyType;
10142     }
10143     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10144     gamesPerCycle = gamesPerRound * roundsPerCycle;
10145     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10146     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10147     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10148     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10149     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10150     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10151
10152     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10153     if(appData.roundSync) *syncInterval = gamesPerRound;
10154
10155     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10156
10157     if(appData.tourneyType == 0) {
10158         if(curPairing == (nPlayers-1)/2 ) {
10159             *whitePlayer = curRound;
10160             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10161         } else {
10162             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10163             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10164             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10165             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10166         }
10167     } else if(appData.tourneyType > 1) {
10168         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10169         *whitePlayer = curRound + appData.tourneyType;
10170     } else if(appData.tourneyType > 0) {
10171         *whitePlayer = curPairing;
10172         *blackPlayer = curRound + appData.tourneyType;
10173     }
10174
10175     // take care of white/black alternation per round. 
10176     // For cycles and games this is already taken care of by default, derived from matchGame!
10177     return curRound & 1;
10178 }
10179
10180 int
10181 NextTourneyGame (int nr, int *swapColors)
10182 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10183     char *p, *q;
10184     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10185     FILE *tf;
10186     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10187     tf = fopen(appData.tourneyFile, "r");
10188     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10189     ParseArgsFromFile(tf); fclose(tf);
10190     InitTimeControls(); // TC might be altered from tourney file
10191
10192     nPlayers = CountPlayers(appData.participants); // count participants
10193     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10194     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10195
10196     if(syncInterval) {
10197         p = q = appData.results;
10198         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10199         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10200             DisplayMessage(_("Waiting for other game(s)"),"");
10201             waitingForGame = TRUE;
10202             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10203             return 0;
10204         }
10205         waitingForGame = FALSE;
10206     }
10207
10208     if(appData.tourneyType < 0) {
10209         if(nr>=0 && !pairingReceived) {
10210             char buf[1<<16];
10211             if(pairing.pr == NoProc) {
10212                 if(!appData.pairingEngine[0]) {
10213                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10214                     return 0;
10215                 }
10216                 StartChessProgram(&pairing); // starts the pairing engine
10217             }
10218             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10219             SendToProgram(buf, &pairing);
10220             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10221             SendToProgram(buf, &pairing);
10222             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10223         }
10224         pairingReceived = 0;                              // ... so we continue here 
10225         *swapColors = 0;
10226         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10227         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10228         matchGame = 1; roundNr = nr / syncInterval + 1;
10229     }
10230
10231     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10232
10233     // redefine engines, engine dir, etc.
10234     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10235     if(first.pr == NoProc) {
10236       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10237       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10238     }
10239     if(second.pr == NoProc) {
10240       SwapEngines(1);
10241       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10242       SwapEngines(1);         // and make that valid for second engine by swapping
10243       InitEngine(&second, 1);
10244     }
10245     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10246     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10247     return 1;
10248 }
10249
10250 void
10251 NextMatchGame ()
10252 {   // performs game initialization that does not invoke engines, and then tries to start the game
10253     int res, firstWhite, swapColors = 0;
10254     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10255     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
10256         char buf[MSG_SIZ];
10257         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10258         if(strcmp(buf, currentDebugFile)) { // name has changed
10259             FILE *f = fopen(buf, "w");
10260             if(f) { // if opening the new file failed, just keep using the old one
10261                 ASSIGN(currentDebugFile, buf);
10262                 fclose(debugFP);
10263                 debugFP = f;
10264             }
10265             if(appData.serverFileName) {
10266                 if(serverFP) fclose(serverFP);
10267                 serverFP = fopen(appData.serverFileName, "w");
10268                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10269                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10270             }
10271         }
10272     }
10273     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10274     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10275     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10276     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10277     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10278     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10279     Reset(FALSE, first.pr != NoProc);
10280     res = LoadGameOrPosition(matchGame); // setup game
10281     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10282     if(!res) return; // abort when bad game/pos file
10283     TwoMachinesEvent();
10284 }
10285
10286 void
10287 UserAdjudicationEvent (int result)
10288 {
10289     ChessMove gameResult = GameIsDrawn;
10290
10291     if( result > 0 ) {
10292         gameResult = WhiteWins;
10293     }
10294     else if( result < 0 ) {
10295         gameResult = BlackWins;
10296     }
10297
10298     if( gameMode == TwoMachinesPlay ) {
10299         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10300     }
10301 }
10302
10303
10304 // [HGM] save: calculate checksum of game to make games easily identifiable
10305 int
10306 StringCheckSum (char *s)
10307 {
10308         int i = 0;
10309         if(s==NULL) return 0;
10310         while(*s) i = i*259 + *s++;
10311         return i;
10312 }
10313
10314 int
10315 GameCheckSum ()
10316 {
10317         int i, sum=0;
10318         for(i=backwardMostMove; i<forwardMostMove; i++) {
10319                 sum += pvInfoList[i].depth;
10320                 sum += StringCheckSum(parseList[i]);
10321                 sum += StringCheckSum(commentList[i]);
10322                 sum *= 261;
10323         }
10324         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10325         return sum + StringCheckSum(commentList[i]);
10326 } // end of save patch
10327
10328 void
10329 GameEnds (ChessMove result, char *resultDetails, int whosays)
10330 {
10331     GameMode nextGameMode;
10332     int isIcsGame;
10333     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10334
10335     if(endingGame) return; /* [HGM] crash: forbid recursion */
10336     endingGame = 1;
10337     if(twoBoards) { // [HGM] dual: switch back to one board
10338         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10339         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10340     }
10341     if (appData.debugMode) {
10342       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10343               result, resultDetails ? resultDetails : "(null)", whosays);
10344     }
10345
10346     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10347
10348     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10349         /* If we are playing on ICS, the server decides when the
10350            game is over, but the engine can offer to draw, claim
10351            a draw, or resign.
10352          */
10353 #if ZIPPY
10354         if (appData.zippyPlay && first.initDone) {
10355             if (result == GameIsDrawn) {
10356                 /* In case draw still needs to be claimed */
10357                 SendToICS(ics_prefix);
10358                 SendToICS("draw\n");
10359             } else if (StrCaseStr(resultDetails, "resign")) {
10360                 SendToICS(ics_prefix);
10361                 SendToICS("resign\n");
10362             }
10363         }
10364 #endif
10365         endingGame = 0; /* [HGM] crash */
10366         return;
10367     }
10368
10369     /* If we're loading the game from a file, stop */
10370     if (whosays == GE_FILE) {
10371       (void) StopLoadGameTimer();
10372       gameFileFP = NULL;
10373     }
10374
10375     /* Cancel draw offers */
10376     first.offeredDraw = second.offeredDraw = 0;
10377
10378     /* If this is an ICS game, only ICS can really say it's done;
10379        if not, anyone can. */
10380     isIcsGame = (gameMode == IcsPlayingWhite ||
10381                  gameMode == IcsPlayingBlack ||
10382                  gameMode == IcsObserving    ||
10383                  gameMode == IcsExamining);
10384
10385     if (!isIcsGame || whosays == GE_ICS) {
10386         /* OK -- not an ICS game, or ICS said it was done */
10387         StopClocks();
10388         if (!isIcsGame && !appData.noChessProgram)
10389           SetUserThinkingEnables();
10390
10391         /* [HGM] if a machine claims the game end we verify this claim */
10392         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10393             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10394                 char claimer;
10395                 ChessMove trueResult = (ChessMove) -1;
10396
10397                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10398                                             first.twoMachinesColor[0] :
10399                                             second.twoMachinesColor[0] ;
10400
10401                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10402                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10403                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10404                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10405                 } else
10406                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10407                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10408                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10409                 } else
10410                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10411                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10412                 }
10413
10414                 // now verify win claims, but not in drop games, as we don't understand those yet
10415                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10416                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10417                     (result == WhiteWins && claimer == 'w' ||
10418                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10419                       if (appData.debugMode) {
10420                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10421                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10422                       }
10423                       if(result != trueResult) {
10424                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10425                               result = claimer == 'w' ? BlackWins : WhiteWins;
10426                               resultDetails = buf;
10427                       }
10428                 } else
10429                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10430                     && (forwardMostMove <= backwardMostMove ||
10431                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10432                         (claimer=='b')==(forwardMostMove&1))
10433                                                                                   ) {
10434                       /* [HGM] verify: draws that were not flagged are false claims */
10435                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10436                       result = claimer == 'w' ? BlackWins : WhiteWins;
10437                       resultDetails = buf;
10438                 }
10439                 /* (Claiming a loss is accepted no questions asked!) */
10440             }
10441             /* [HGM] bare: don't allow bare King to win */
10442             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10443                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10444                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10445                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10446                && result != GameIsDrawn)
10447             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10448                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10449                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10450                         if(p >= 0 && p <= (int)WhiteKing) k++;
10451                 }
10452                 if (appData.debugMode) {
10453                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10454                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10455                 }
10456                 if(k <= 1) {
10457                         result = GameIsDrawn;
10458                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10459                         resultDetails = buf;
10460                 }
10461             }
10462         }
10463
10464
10465         if(serverMoves != NULL && !loadFlag) { char c = '=';
10466             if(result==WhiteWins) c = '+';
10467             if(result==BlackWins) c = '-';
10468             if(resultDetails != NULL)
10469                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10470         }
10471         if (resultDetails != NULL) {
10472             gameInfo.result = result;
10473             gameInfo.resultDetails = StrSave(resultDetails);
10474
10475             /* display last move only if game was not loaded from file */
10476             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10477                 DisplayMove(currentMove - 1);
10478
10479             if (forwardMostMove != 0) {
10480                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10481                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10482                                                                 ) {
10483                     if (*appData.saveGameFile != NULLCHAR) {
10484                         SaveGameToFile(appData.saveGameFile, TRUE);
10485                     } else if (appData.autoSaveGames) {
10486                         AutoSaveGame();
10487                     }
10488                     if (*appData.savePositionFile != NULLCHAR) {
10489                         SavePositionToFile(appData.savePositionFile);
10490                     }
10491                 }
10492             }
10493
10494             /* Tell program how game ended in case it is learning */
10495             /* [HGM] Moved this to after saving the PGN, just in case */
10496             /* engine died and we got here through time loss. In that */
10497             /* case we will get a fatal error writing the pipe, which */
10498             /* would otherwise lose us the PGN.                       */
10499             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10500             /* output during GameEnds should never be fatal anymore   */
10501             if (gameMode == MachinePlaysWhite ||
10502                 gameMode == MachinePlaysBlack ||
10503                 gameMode == TwoMachinesPlay ||
10504                 gameMode == IcsPlayingWhite ||
10505                 gameMode == IcsPlayingBlack ||
10506                 gameMode == BeginningOfGame) {
10507                 char buf[MSG_SIZ];
10508                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10509                         resultDetails);
10510                 if (first.pr != NoProc) {
10511                     SendToProgram(buf, &first);
10512                 }
10513                 if (second.pr != NoProc &&
10514                     gameMode == TwoMachinesPlay) {
10515                     SendToProgram(buf, &second);
10516                 }
10517             }
10518         }
10519
10520         if (appData.icsActive) {
10521             if (appData.quietPlay &&
10522                 (gameMode == IcsPlayingWhite ||
10523                  gameMode == IcsPlayingBlack)) {
10524                 SendToICS(ics_prefix);
10525                 SendToICS("set shout 1\n");
10526             }
10527             nextGameMode = IcsIdle;
10528             ics_user_moved = FALSE;
10529             /* clean up premove.  It's ugly when the game has ended and the
10530              * premove highlights are still on the board.
10531              */
10532             if (gotPremove) {
10533               gotPremove = FALSE;
10534               ClearPremoveHighlights();
10535               DrawPosition(FALSE, boards[currentMove]);
10536             }
10537             if (whosays == GE_ICS) {
10538                 switch (result) {
10539                 case WhiteWins:
10540                     if (gameMode == IcsPlayingWhite)
10541                         PlayIcsWinSound();
10542                     else if(gameMode == IcsPlayingBlack)
10543                         PlayIcsLossSound();
10544                     break;
10545                 case BlackWins:
10546                     if (gameMode == IcsPlayingBlack)
10547                         PlayIcsWinSound();
10548                     else if(gameMode == IcsPlayingWhite)
10549                         PlayIcsLossSound();
10550                     break;
10551                 case GameIsDrawn:
10552                     PlayIcsDrawSound();
10553                     break;
10554                 default:
10555                     PlayIcsUnfinishedSound();
10556                 }
10557             }
10558         } else if (gameMode == EditGame ||
10559                    gameMode == PlayFromGameFile ||
10560                    gameMode == AnalyzeMode ||
10561                    gameMode == AnalyzeFile) {
10562             nextGameMode = gameMode;
10563         } else {
10564             nextGameMode = EndOfGame;
10565         }
10566         pausing = FALSE;
10567         ModeHighlight();
10568     } else {
10569         nextGameMode = gameMode;
10570     }
10571
10572     if (appData.noChessProgram) {
10573         gameMode = nextGameMode;
10574         ModeHighlight();
10575         endingGame = 0; /* [HGM] crash */
10576         return;
10577     }
10578
10579     if (first.reuse) {
10580         /* Put first chess program into idle state */
10581         if (first.pr != NoProc &&
10582             (gameMode == MachinePlaysWhite ||
10583              gameMode == MachinePlaysBlack ||
10584              gameMode == TwoMachinesPlay ||
10585              gameMode == IcsPlayingWhite ||
10586              gameMode == IcsPlayingBlack ||
10587              gameMode == BeginningOfGame)) {
10588             SendToProgram("force\n", &first);
10589             if (first.usePing) {
10590               char buf[MSG_SIZ];
10591               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10592               SendToProgram(buf, &first);
10593             }
10594         }
10595     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10596         /* Kill off first chess program */
10597         if (first.isr != NULL)
10598           RemoveInputSource(first.isr);
10599         first.isr = NULL;
10600
10601         if (first.pr != NoProc) {
10602             ExitAnalyzeMode();
10603             DoSleep( appData.delayBeforeQuit );
10604             SendToProgram("quit\n", &first);
10605             DoSleep( appData.delayAfterQuit );
10606             DestroyChildProcess(first.pr, first.useSigterm);
10607         }
10608         first.pr = NoProc;
10609     }
10610     if (second.reuse) {
10611         /* Put second chess program into idle state */
10612         if (second.pr != NoProc &&
10613             gameMode == TwoMachinesPlay) {
10614             SendToProgram("force\n", &second);
10615             if (second.usePing) {
10616               char buf[MSG_SIZ];
10617               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10618               SendToProgram(buf, &second);
10619             }
10620         }
10621     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10622         /* Kill off second chess program */
10623         if (second.isr != NULL)
10624           RemoveInputSource(second.isr);
10625         second.isr = NULL;
10626
10627         if (second.pr != NoProc) {
10628             DoSleep( appData.delayBeforeQuit );
10629             SendToProgram("quit\n", &second);
10630             DoSleep( appData.delayAfterQuit );
10631             DestroyChildProcess(second.pr, second.useSigterm);
10632         }
10633         second.pr = NoProc;
10634     }
10635
10636     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10637         char resChar = '=';
10638         switch (result) {
10639         case WhiteWins:
10640           resChar = '+';
10641           if (first.twoMachinesColor[0] == 'w') {
10642             first.matchWins++;
10643           } else {
10644             second.matchWins++;
10645           }
10646           break;
10647         case BlackWins:
10648           resChar = '-';
10649           if (first.twoMachinesColor[0] == 'b') {
10650             first.matchWins++;
10651           } else {
10652             second.matchWins++;
10653           }
10654           break;
10655         case GameUnfinished:
10656           resChar = ' ';
10657         default:
10658           break;
10659         }
10660
10661         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10662         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10663             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10664             ReserveGame(nextGame, resChar); // sets nextGame
10665             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10666             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10667         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10668
10669         if (nextGame <= appData.matchGames && !abortMatch) {
10670             gameMode = nextGameMode;
10671             matchGame = nextGame; // this will be overruled in tourney mode!
10672             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10673             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10674             endingGame = 0; /* [HGM] crash */
10675             return;
10676         } else {
10677             gameMode = nextGameMode;
10678             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10679                      first.tidy, second.tidy,
10680                      first.matchWins, second.matchWins,
10681                      appData.matchGames - (first.matchWins + second.matchWins));
10682             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10683             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10684             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10685             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10686                 first.twoMachinesColor = "black\n";
10687                 second.twoMachinesColor = "white\n";
10688             } else {
10689                 first.twoMachinesColor = "white\n";
10690                 second.twoMachinesColor = "black\n";
10691             }
10692         }
10693     }
10694     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10695         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10696       ExitAnalyzeMode();
10697     gameMode = nextGameMode;
10698     ModeHighlight();
10699     endingGame = 0;  /* [HGM] crash */
10700     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10701         if(matchMode == TRUE) { // match through command line: exit with or without popup
10702             if(ranking) {
10703                 ToNrEvent(forwardMostMove);
10704                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10705                 else ExitEvent(0);
10706             } else DisplayFatalError(buf, 0, 0);
10707         } else { // match through menu; just stop, with or without popup
10708             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10709             ModeHighlight();
10710             if(ranking){
10711                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10712             } else DisplayNote(buf);
10713       }
10714       if(ranking) free(ranking);
10715     }
10716 }
10717
10718 /* Assumes program was just initialized (initString sent).
10719    Leaves program in force mode. */
10720 void
10721 FeedMovesToProgram (ChessProgramState *cps, int upto)
10722 {
10723     int i;
10724
10725     if (appData.debugMode)
10726       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10727               startedFromSetupPosition ? "position and " : "",
10728               backwardMostMove, upto, cps->which);
10729     if(currentlyInitializedVariant != gameInfo.variant) {
10730       char buf[MSG_SIZ];
10731         // [HGM] variantswitch: make engine aware of new variant
10732         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10733                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10734         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10735         SendToProgram(buf, cps);
10736         currentlyInitializedVariant = gameInfo.variant;
10737     }
10738     SendToProgram("force\n", cps);
10739     if (startedFromSetupPosition) {
10740         SendBoard(cps, backwardMostMove);
10741     if (appData.debugMode) {
10742         fprintf(debugFP, "feedMoves\n");
10743     }
10744     }
10745     for (i = backwardMostMove; i < upto; i++) {
10746         SendMoveToProgram(i, cps);
10747     }
10748 }
10749
10750
10751 int
10752 ResurrectChessProgram ()
10753 {
10754      /* The chess program may have exited.
10755         If so, restart it and feed it all the moves made so far. */
10756     static int doInit = 0;
10757
10758     if (appData.noChessProgram) return 1;
10759
10760     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10761         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10762         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10763         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10764     } else {
10765         if (first.pr != NoProc) return 1;
10766         StartChessProgram(&first);
10767     }
10768     InitChessProgram(&first, FALSE);
10769     FeedMovesToProgram(&first, currentMove);
10770
10771     if (!first.sendTime) {
10772         /* can't tell gnuchess what its clock should read,
10773            so we bow to its notion. */
10774         ResetClocks();
10775         timeRemaining[0][currentMove] = whiteTimeRemaining;
10776         timeRemaining[1][currentMove] = blackTimeRemaining;
10777     }
10778
10779     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10780                 appData.icsEngineAnalyze) && first.analysisSupport) {
10781       SendToProgram("analyze\n", &first);
10782       first.analyzing = TRUE;
10783     }
10784     return 1;
10785 }
10786
10787 /*
10788  * Button procedures
10789  */
10790 void
10791 Reset (int redraw, int init)
10792 {
10793     int i;
10794
10795     if (appData.debugMode) {
10796         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10797                 redraw, init, gameMode);
10798     }
10799     CleanupTail(); // [HGM] vari: delete any stored variations
10800     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10801     pausing = pauseExamInvalid = FALSE;
10802     startedFromSetupPosition = blackPlaysFirst = FALSE;
10803     firstMove = TRUE;
10804     whiteFlag = blackFlag = FALSE;
10805     userOfferedDraw = FALSE;
10806     hintRequested = bookRequested = FALSE;
10807     first.maybeThinking = FALSE;
10808     second.maybeThinking = FALSE;
10809     first.bookSuspend = FALSE; // [HGM] book
10810     second.bookSuspend = FALSE;
10811     thinkOutput[0] = NULLCHAR;
10812     lastHint[0] = NULLCHAR;
10813     ClearGameInfo(&gameInfo);
10814     gameInfo.variant = StringToVariant(appData.variant);
10815     ics_user_moved = ics_clock_paused = FALSE;
10816     ics_getting_history = H_FALSE;
10817     ics_gamenum = -1;
10818     white_holding[0] = black_holding[0] = NULLCHAR;
10819     ClearProgramStats();
10820     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10821
10822     ResetFrontEnd();
10823     ClearHighlights();
10824     flipView = appData.flipView;
10825     ClearPremoveHighlights();
10826     gotPremove = FALSE;
10827     alarmSounded = FALSE;
10828
10829     GameEnds(EndOfFile, NULL, GE_PLAYER);
10830     if(appData.serverMovesName != NULL) {
10831         /* [HGM] prepare to make moves file for broadcasting */
10832         clock_t t = clock();
10833         if(serverMoves != NULL) fclose(serverMoves);
10834         serverMoves = fopen(appData.serverMovesName, "r");
10835         if(serverMoves != NULL) {
10836             fclose(serverMoves);
10837             /* delay 15 sec before overwriting, so all clients can see end */
10838             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10839         }
10840         serverMoves = fopen(appData.serverMovesName, "w");
10841     }
10842
10843     ExitAnalyzeMode();
10844     gameMode = BeginningOfGame;
10845     ModeHighlight();
10846     if(appData.icsActive) gameInfo.variant = VariantNormal;
10847     currentMove = forwardMostMove = backwardMostMove = 0;
10848     MarkTargetSquares(1);
10849     InitPosition(redraw);
10850     for (i = 0; i < MAX_MOVES; i++) {
10851         if (commentList[i] != NULL) {
10852             free(commentList[i]);
10853             commentList[i] = NULL;
10854         }
10855     }
10856     ResetClocks();
10857     timeRemaining[0][0] = whiteTimeRemaining;
10858     timeRemaining[1][0] = blackTimeRemaining;
10859
10860     if (first.pr == NoProc) {
10861         StartChessProgram(&first);
10862     }
10863     if (init) {
10864             InitChessProgram(&first, startedFromSetupPosition);
10865     }
10866     DisplayTitle("");
10867     DisplayMessage("", "");
10868     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10869     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10870     ClearMap();        // [HGM] exclude: invalidate map
10871 }
10872
10873 void
10874 AutoPlayGameLoop ()
10875 {
10876     for (;;) {
10877         if (!AutoPlayOneMove())
10878           return;
10879         if (matchMode || appData.timeDelay == 0)
10880           continue;
10881         if (appData.timeDelay < 0)
10882           return;
10883         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10884         break;
10885     }
10886 }
10887
10888
10889 int
10890 AutoPlayOneMove ()
10891 {
10892     int fromX, fromY, toX, toY;
10893
10894     if (appData.debugMode) {
10895       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10896     }
10897
10898     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10899       return FALSE;
10900
10901     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10902       pvInfoList[currentMove].depth = programStats.depth;
10903       pvInfoList[currentMove].score = programStats.score;
10904       pvInfoList[currentMove].time  = 0;
10905       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10906     }
10907
10908     if (currentMove >= forwardMostMove) {
10909       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10910 //      gameMode = EndOfGame;
10911 //      ModeHighlight();
10912
10913       /* [AS] Clear current move marker at the end of a game */
10914       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10915
10916       return FALSE;
10917     }
10918
10919     toX = moveList[currentMove][2] - AAA;
10920     toY = moveList[currentMove][3] - ONE;
10921
10922     if (moveList[currentMove][1] == '@') {
10923         if (appData.highlightLastMove) {
10924             SetHighlights(-1, -1, toX, toY);
10925         }
10926     } else {
10927         fromX = moveList[currentMove][0] - AAA;
10928         fromY = moveList[currentMove][1] - ONE;
10929
10930         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10931
10932         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10933
10934         if (appData.highlightLastMove) {
10935             SetHighlights(fromX, fromY, toX, toY);
10936         }
10937     }
10938     DisplayMove(currentMove);
10939     SendMoveToProgram(currentMove++, &first);
10940     DisplayBothClocks();
10941     DrawPosition(FALSE, boards[currentMove]);
10942     // [HGM] PV info: always display, routine tests if empty
10943     DisplayComment(currentMove - 1, commentList[currentMove]);
10944     return TRUE;
10945 }
10946
10947
10948 int
10949 LoadGameOneMove (ChessMove readAhead)
10950 {
10951     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10952     char promoChar = NULLCHAR;
10953     ChessMove moveType;
10954     char move[MSG_SIZ];
10955     char *p, *q;
10956
10957     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10958         gameMode != AnalyzeMode && gameMode != Training) {
10959         gameFileFP = NULL;
10960         return FALSE;
10961     }
10962
10963     yyboardindex = forwardMostMove;
10964     if (readAhead != EndOfFile) {
10965       moveType = readAhead;
10966     } else {
10967       if (gameFileFP == NULL)
10968           return FALSE;
10969       moveType = (ChessMove) Myylex();
10970     }
10971
10972     done = FALSE;
10973     switch (moveType) {
10974       case Comment:
10975         if (appData.debugMode)
10976           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10977         p = yy_text;
10978
10979         /* append the comment but don't display it */
10980         AppendComment(currentMove, p, FALSE);
10981         return TRUE;
10982
10983       case WhiteCapturesEnPassant:
10984       case BlackCapturesEnPassant:
10985       case WhitePromotion:
10986       case BlackPromotion:
10987       case WhiteNonPromotion:
10988       case BlackNonPromotion:
10989       case NormalMove:
10990       case WhiteKingSideCastle:
10991       case WhiteQueenSideCastle:
10992       case BlackKingSideCastle:
10993       case BlackQueenSideCastle:
10994       case WhiteKingSideCastleWild:
10995       case WhiteQueenSideCastleWild:
10996       case BlackKingSideCastleWild:
10997       case BlackQueenSideCastleWild:
10998       /* PUSH Fabien */
10999       case WhiteHSideCastleFR:
11000       case WhiteASideCastleFR:
11001       case BlackHSideCastleFR:
11002       case BlackASideCastleFR:
11003       /* POP Fabien */
11004         if (appData.debugMode)
11005           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11006         fromX = currentMoveString[0] - AAA;
11007         fromY = currentMoveString[1] - ONE;
11008         toX = currentMoveString[2] - AAA;
11009         toY = currentMoveString[3] - ONE;
11010         promoChar = currentMoveString[4];
11011         break;
11012
11013       case WhiteDrop:
11014       case BlackDrop:
11015         if (appData.debugMode)
11016           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11017         fromX = moveType == WhiteDrop ?
11018           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11019         (int) CharToPiece(ToLower(currentMoveString[0]));
11020         fromY = DROP_RANK;
11021         toX = currentMoveString[2] - AAA;
11022         toY = currentMoveString[3] - ONE;
11023         break;
11024
11025       case WhiteWins:
11026       case BlackWins:
11027       case GameIsDrawn:
11028       case GameUnfinished:
11029         if (appData.debugMode)
11030           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11031         p = strchr(yy_text, '{');
11032         if (p == NULL) p = strchr(yy_text, '(');
11033         if (p == NULL) {
11034             p = yy_text;
11035             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11036         } else {
11037             q = strchr(p, *p == '{' ? '}' : ')');
11038             if (q != NULL) *q = NULLCHAR;
11039             p++;
11040         }
11041         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11042         GameEnds(moveType, p, GE_FILE);
11043         done = TRUE;
11044         if (cmailMsgLoaded) {
11045             ClearHighlights();
11046             flipView = WhiteOnMove(currentMove);
11047             if (moveType == GameUnfinished) flipView = !flipView;
11048             if (appData.debugMode)
11049               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11050         }
11051         break;
11052
11053       case EndOfFile:
11054         if (appData.debugMode)
11055           fprintf(debugFP, "Parser hit end of file\n");
11056         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11057           case MT_NONE:
11058           case MT_CHECK:
11059             break;
11060           case MT_CHECKMATE:
11061           case MT_STAINMATE:
11062             if (WhiteOnMove(currentMove)) {
11063                 GameEnds(BlackWins, "Black mates", GE_FILE);
11064             } else {
11065                 GameEnds(WhiteWins, "White mates", GE_FILE);
11066             }
11067             break;
11068           case MT_STALEMATE:
11069             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11070             break;
11071         }
11072         done = TRUE;
11073         break;
11074
11075       case MoveNumberOne:
11076         if (lastLoadGameStart == GNUChessGame) {
11077             /* GNUChessGames have numbers, but they aren't move numbers */
11078             if (appData.debugMode)
11079               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11080                       yy_text, (int) moveType);
11081             return LoadGameOneMove(EndOfFile); /* tail recursion */
11082         }
11083         /* else fall thru */
11084
11085       case XBoardGame:
11086       case GNUChessGame:
11087       case PGNTag:
11088         /* Reached start of next game in file */
11089         if (appData.debugMode)
11090           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11091         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11092           case MT_NONE:
11093           case MT_CHECK:
11094             break;
11095           case MT_CHECKMATE:
11096           case MT_STAINMATE:
11097             if (WhiteOnMove(currentMove)) {
11098                 GameEnds(BlackWins, "Black mates", GE_FILE);
11099             } else {
11100                 GameEnds(WhiteWins, "White mates", GE_FILE);
11101             }
11102             break;
11103           case MT_STALEMATE:
11104             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11105             break;
11106         }
11107         done = TRUE;
11108         break;
11109
11110       case PositionDiagram:     /* should not happen; ignore */
11111       case ElapsedTime:         /* ignore */
11112       case NAG:                 /* ignore */
11113         if (appData.debugMode)
11114           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11115                   yy_text, (int) moveType);
11116         return LoadGameOneMove(EndOfFile); /* tail recursion */
11117
11118       case IllegalMove:
11119         if (appData.testLegality) {
11120             if (appData.debugMode)
11121               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11122             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11123                     (forwardMostMove / 2) + 1,
11124                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11125             DisplayError(move, 0);
11126             done = TRUE;
11127         } else {
11128             if (appData.debugMode)
11129               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11130                       yy_text, currentMoveString);
11131             fromX = currentMoveString[0] - AAA;
11132             fromY = currentMoveString[1] - ONE;
11133             toX = currentMoveString[2] - AAA;
11134             toY = currentMoveString[3] - ONE;
11135             promoChar = currentMoveString[4];
11136         }
11137         break;
11138
11139       case AmbiguousMove:
11140         if (appData.debugMode)
11141           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11142         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11143                 (forwardMostMove / 2) + 1,
11144                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11145         DisplayError(move, 0);
11146         done = TRUE;
11147         break;
11148
11149       default:
11150       case ImpossibleMove:
11151         if (appData.debugMode)
11152           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11153         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11154                 (forwardMostMove / 2) + 1,
11155                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11156         DisplayError(move, 0);
11157         done = TRUE;
11158         break;
11159     }
11160
11161     if (done) {
11162         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11163             DrawPosition(FALSE, boards[currentMove]);
11164             DisplayBothClocks();
11165             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11166               DisplayComment(currentMove - 1, commentList[currentMove]);
11167         }
11168         (void) StopLoadGameTimer();
11169         gameFileFP = NULL;
11170         cmailOldMove = forwardMostMove;
11171         return FALSE;
11172     } else {
11173         /* currentMoveString is set as a side-effect of yylex */
11174
11175         thinkOutput[0] = NULLCHAR;
11176         MakeMove(fromX, fromY, toX, toY, promoChar);
11177         currentMove = forwardMostMove;
11178         return TRUE;
11179     }
11180 }
11181
11182 /* Load the nth game from the given file */
11183 int
11184 LoadGameFromFile (char *filename, int n, char *title, int useList)
11185 {
11186     FILE *f;
11187     char buf[MSG_SIZ];
11188
11189     if (strcmp(filename, "-") == 0) {
11190         f = stdin;
11191         title = "stdin";
11192     } else {
11193         f = fopen(filename, "rb");
11194         if (f == NULL) {
11195           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11196             DisplayError(buf, errno);
11197             return FALSE;
11198         }
11199     }
11200     if (fseek(f, 0, 0) == -1) {
11201         /* f is not seekable; probably a pipe */
11202         useList = FALSE;
11203     }
11204     if (useList && n == 0) {
11205         int error = GameListBuild(f);
11206         if (error) {
11207             DisplayError(_("Cannot build game list"), error);
11208         } else if (!ListEmpty(&gameList) &&
11209                    ((ListGame *) gameList.tailPred)->number > 1) {
11210             GameListPopUp(f, title);
11211             return TRUE;
11212         }
11213         GameListDestroy();
11214         n = 1;
11215     }
11216     if (n == 0) n = 1;
11217     return LoadGame(f, n, title, FALSE);
11218 }
11219
11220
11221 void
11222 MakeRegisteredMove ()
11223 {
11224     int fromX, fromY, toX, toY;
11225     char promoChar;
11226     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11227         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11228           case CMAIL_MOVE:
11229           case CMAIL_DRAW:
11230             if (appData.debugMode)
11231               fprintf(debugFP, "Restoring %s for game %d\n",
11232                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11233
11234             thinkOutput[0] = NULLCHAR;
11235             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11236             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11237             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11238             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11239             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11240             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11241             MakeMove(fromX, fromY, toX, toY, promoChar);
11242             ShowMove(fromX, fromY, toX, toY);
11243
11244             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11245               case MT_NONE:
11246               case MT_CHECK:
11247                 break;
11248
11249               case MT_CHECKMATE:
11250               case MT_STAINMATE:
11251                 if (WhiteOnMove(currentMove)) {
11252                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11253                 } else {
11254                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11255                 }
11256                 break;
11257
11258               case MT_STALEMATE:
11259                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11260                 break;
11261             }
11262
11263             break;
11264
11265           case CMAIL_RESIGN:
11266             if (WhiteOnMove(currentMove)) {
11267                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11268             } else {
11269                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11270             }
11271             break;
11272
11273           case CMAIL_ACCEPT:
11274             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11275             break;
11276
11277           default:
11278             break;
11279         }
11280     }
11281
11282     return;
11283 }
11284
11285 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11286 int
11287 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11288 {
11289     int retVal;
11290
11291     if (gameNumber > nCmailGames) {
11292         DisplayError(_("No more games in this message"), 0);
11293         return FALSE;
11294     }
11295     if (f == lastLoadGameFP) {
11296         int offset = gameNumber - lastLoadGameNumber;
11297         if (offset == 0) {
11298             cmailMsg[0] = NULLCHAR;
11299             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11300                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11301                 nCmailMovesRegistered--;
11302             }
11303             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11304             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11305                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11306             }
11307         } else {
11308             if (! RegisterMove()) return FALSE;
11309         }
11310     }
11311
11312     retVal = LoadGame(f, gameNumber, title, useList);
11313
11314     /* Make move registered during previous look at this game, if any */
11315     MakeRegisteredMove();
11316
11317     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11318         commentList[currentMove]
11319           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11320         DisplayComment(currentMove - 1, commentList[currentMove]);
11321     }
11322
11323     return retVal;
11324 }
11325
11326 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11327 int
11328 ReloadGame (int offset)
11329 {
11330     int gameNumber = lastLoadGameNumber + offset;
11331     if (lastLoadGameFP == NULL) {
11332         DisplayError(_("No game has been loaded yet"), 0);
11333         return FALSE;
11334     }
11335     if (gameNumber <= 0) {
11336         DisplayError(_("Can't back up any further"), 0);
11337         return FALSE;
11338     }
11339     if (cmailMsgLoaded) {
11340         return CmailLoadGame(lastLoadGameFP, gameNumber,
11341                              lastLoadGameTitle, lastLoadGameUseList);
11342     } else {
11343         return LoadGame(lastLoadGameFP, gameNumber,
11344                         lastLoadGameTitle, lastLoadGameUseList);
11345     }
11346 }
11347
11348 int keys[EmptySquare+1];
11349
11350 int
11351 PositionMatches (Board b1, Board b2)
11352 {
11353     int r, f, sum=0;
11354     switch(appData.searchMode) {
11355         case 1: return CompareWithRights(b1, b2);
11356         case 2:
11357             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11358                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11359             }
11360             return TRUE;
11361         case 3:
11362             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11363               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11364                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11365             }
11366             return sum==0;
11367         case 4:
11368             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11369                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11370             }
11371             return sum==0;
11372     }
11373     return TRUE;
11374 }
11375
11376 #define Q_PROMO  4
11377 #define Q_EP     3
11378 #define Q_BCASTL 2
11379 #define Q_WCASTL 1
11380
11381 int pieceList[256], quickBoard[256];
11382 ChessSquare pieceType[256] = { EmptySquare };
11383 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11384 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11385 int soughtTotal, turn;
11386 Boolean epOK, flipSearch;
11387
11388 typedef struct {
11389     unsigned char piece, to;
11390 } Move;
11391
11392 #define DSIZE (250000)
11393
11394 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11395 Move *moveDatabase = initialSpace;
11396 unsigned int movePtr, dataSize = DSIZE;
11397
11398 int
11399 MakePieceList (Board board, int *counts)
11400 {
11401     int r, f, n=Q_PROMO, total=0;
11402     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11403     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11404         int sq = f + (r<<4);
11405         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11406             quickBoard[sq] = ++n;
11407             pieceList[n] = sq;
11408             pieceType[n] = board[r][f];
11409             counts[board[r][f]]++;
11410             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11411             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11412             total++;
11413         }
11414     }
11415     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11416     return total;
11417 }
11418
11419 void
11420 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11421 {
11422     int sq = fromX + (fromY<<4);
11423     int piece = quickBoard[sq];
11424     quickBoard[sq] = 0;
11425     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11426     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11427         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11428         moveDatabase[movePtr++].piece = Q_WCASTL;
11429         quickBoard[sq] = piece;
11430         piece = quickBoard[from]; quickBoard[from] = 0;
11431         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11432     } else
11433     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11434         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11435         moveDatabase[movePtr++].piece = Q_BCASTL;
11436         quickBoard[sq] = piece;
11437         piece = quickBoard[from]; quickBoard[from] = 0;
11438         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11439     } else
11440     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11441         quickBoard[(fromY<<4)+toX] = 0;
11442         moveDatabase[movePtr].piece = Q_EP;
11443         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11444         moveDatabase[movePtr].to = sq;
11445     } else
11446     if(promoPiece != pieceType[piece]) {
11447         moveDatabase[movePtr++].piece = Q_PROMO;
11448         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11449     }
11450     moveDatabase[movePtr].piece = piece;
11451     quickBoard[sq] = piece;
11452     movePtr++;
11453 }
11454
11455 int
11456 PackGame (Board board)
11457 {
11458     Move *newSpace = NULL;
11459     moveDatabase[movePtr].piece = 0; // terminate previous game
11460     if(movePtr > dataSize) {
11461         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11462         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11463         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11464         if(newSpace) {
11465             int i;
11466             Move *p = moveDatabase, *q = newSpace;
11467             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11468             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11469             moveDatabase = newSpace;
11470         } else { // calloc failed, we must be out of memory. Too bad...
11471             dataSize = 0; // prevent calloc events for all subsequent games
11472             return 0;     // and signal this one isn't cached
11473         }
11474     }
11475     movePtr++;
11476     MakePieceList(board, counts);
11477     return movePtr;
11478 }
11479
11480 int
11481 QuickCompare (Board board, int *minCounts, int *maxCounts)
11482 {   // compare according to search mode
11483     int r, f;
11484     switch(appData.searchMode)
11485     {
11486       case 1: // exact position match
11487         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11488         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11489             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11490         }
11491         break;
11492       case 2: // can have extra material on empty squares
11493         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11494             if(board[r][f] == EmptySquare) continue;
11495             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11496         }
11497         break;
11498       case 3: // material with exact Pawn structure
11499         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11500             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11501             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11502         } // fall through to material comparison
11503       case 4: // exact material
11504         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11505         break;
11506       case 6: // material range with given imbalance
11507         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11508         // fall through to range comparison
11509       case 5: // material range
11510         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11511     }
11512     return TRUE;
11513 }
11514
11515 int
11516 QuickScan (Board board, Move *move)
11517 {   // reconstruct game,and compare all positions in it
11518     int cnt=0, stretch=0, total = MakePieceList(board, counts), delayedKing = -1;
11519     do {
11520         int piece = move->piece;
11521         int to = move->to, from = pieceList[piece];
11522         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11523           if(!piece) return -1;
11524           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11525             piece = (++move)->piece;
11526             from = pieceList[piece];
11527             counts[pieceType[piece]]--;
11528             pieceType[piece] = (ChessSquare) move->to;
11529             counts[move->to]++;
11530           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11531             counts[pieceType[quickBoard[to]]]--;
11532             quickBoard[to] = 0; total--;
11533             move++;
11534             continue;
11535           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11536             int rook;
11537             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11538             from  = pieceList[piece]; // so this must be King
11539             quickBoard[from] = 0;
11540             pieceList[piece] = to;
11541             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11542             quickBoard[from] = 0; // rook
11543             quickBoard[to] = piece;
11544             to = move->to; piece = move->piece;
11545             goto aftercastle;
11546           }
11547         }
11548         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11549         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11550         quickBoard[from] = 0;
11551       aftercastle:
11552         quickBoard[to] = piece;
11553         pieceList[piece] = to;
11554         cnt++; turn ^= 3;
11555         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11556            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11557            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11558                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11559           ) {
11560             static int lastCounts[EmptySquare+1];
11561             int i;
11562             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11563             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11564         } else stretch = 0;
11565         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11566         move++; delayedKing = -1;
11567     } while(1);
11568 }
11569
11570 void
11571 InitSearch ()
11572 {
11573     int r, f;
11574     flipSearch = FALSE;
11575     CopyBoard(soughtBoard, boards[currentMove]);
11576     soughtTotal = MakePieceList(soughtBoard, maxSought);
11577     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11578     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11579     CopyBoard(reverseBoard, boards[currentMove]);
11580     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11581         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11582         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11583         reverseBoard[r][f] = piece;
11584     }
11585     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11586     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11587     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11588                  || (boards[currentMove][CASTLING][2] == NoRights || 
11589                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11590                  && (boards[currentMove][CASTLING][5] == NoRights || 
11591                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11592       ) {
11593         flipSearch = TRUE;
11594         CopyBoard(flipBoard, soughtBoard);
11595         CopyBoard(rotateBoard, reverseBoard);
11596         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11597             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11598             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11599         }
11600     }
11601     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11602     if(appData.searchMode >= 5) {
11603         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11604         MakePieceList(soughtBoard, minSought);
11605         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11606     }
11607     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11608         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11609 }
11610
11611 GameInfo dummyInfo;
11612
11613 int
11614 GameContainsPosition (FILE *f, ListGame *lg)
11615 {
11616     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11617     int fromX, fromY, toX, toY;
11618     char promoChar;
11619     static int initDone=FALSE;
11620
11621     // weed out games based on numerical tag comparison
11622     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11623     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11624     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11625     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11626     if(!initDone) {
11627         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11628         initDone = TRUE;
11629     }
11630     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11631     else CopyBoard(boards[scratch], initialPosition); // default start position
11632     if(lg->moves) {
11633         turn = btm + 1;
11634         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11635         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11636     }
11637     if(btm) plyNr++;
11638     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11639     fseek(f, lg->offset, 0);
11640     yynewfile(f);
11641     while(1) {
11642         yyboardindex = scratch;
11643         quickFlag = plyNr+1;
11644         next = Myylex();
11645         quickFlag = 0;
11646         switch(next) {
11647             case PGNTag:
11648                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11649             default:
11650                 continue;
11651
11652             case XBoardGame:
11653             case GNUChessGame:
11654                 if(plyNr) return -1; // after we have seen moves, this is for new game
11655               continue;
11656
11657             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11658             case ImpossibleMove:
11659             case WhiteWins: // game ends here with these four
11660             case BlackWins:
11661             case GameIsDrawn:
11662             case GameUnfinished:
11663                 return -1;
11664
11665             case IllegalMove:
11666                 if(appData.testLegality) return -1;
11667             case WhiteCapturesEnPassant:
11668             case BlackCapturesEnPassant:
11669             case WhitePromotion:
11670             case BlackPromotion:
11671             case WhiteNonPromotion:
11672             case BlackNonPromotion:
11673             case NormalMove:
11674             case WhiteKingSideCastle:
11675             case WhiteQueenSideCastle:
11676             case BlackKingSideCastle:
11677             case BlackQueenSideCastle:
11678             case WhiteKingSideCastleWild:
11679             case WhiteQueenSideCastleWild:
11680             case BlackKingSideCastleWild:
11681             case BlackQueenSideCastleWild:
11682             case WhiteHSideCastleFR:
11683             case WhiteASideCastleFR:
11684             case BlackHSideCastleFR:
11685             case BlackASideCastleFR:
11686                 fromX = currentMoveString[0] - AAA;
11687                 fromY = currentMoveString[1] - ONE;
11688                 toX = currentMoveString[2] - AAA;
11689                 toY = currentMoveString[3] - ONE;
11690                 promoChar = currentMoveString[4];
11691                 break;
11692             case WhiteDrop:
11693             case BlackDrop:
11694                 fromX = next == WhiteDrop ?
11695                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11696                   (int) CharToPiece(ToLower(currentMoveString[0]));
11697                 fromY = DROP_RANK;
11698                 toX = currentMoveString[2] - AAA;
11699                 toY = currentMoveString[3] - ONE;
11700                 promoChar = 0;
11701                 break;
11702         }
11703         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11704         plyNr++;
11705         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11706         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11707         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11708         if(appData.findMirror) {
11709             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11710             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11711         }
11712     }
11713 }
11714
11715 /* Load the nth game from open file f */
11716 int
11717 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11718 {
11719     ChessMove cm;
11720     char buf[MSG_SIZ];
11721     int gn = gameNumber;
11722     ListGame *lg = NULL;
11723     int numPGNTags = 0;
11724     int err, pos = -1;
11725     GameMode oldGameMode;
11726     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11727
11728     if (appData.debugMode)
11729         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11730
11731     if (gameMode == Training )
11732         SetTrainingModeOff();
11733
11734     oldGameMode = gameMode;
11735     if (gameMode != BeginningOfGame) {
11736       Reset(FALSE, TRUE);
11737     }
11738
11739     gameFileFP = f;
11740     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11741         fclose(lastLoadGameFP);
11742     }
11743
11744     if (useList) {
11745         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11746
11747         if (lg) {
11748             fseek(f, lg->offset, 0);
11749             GameListHighlight(gameNumber);
11750             pos = lg->position;
11751             gn = 1;
11752         }
11753         else {
11754             DisplayError(_("Game number out of range"), 0);
11755             return FALSE;
11756         }
11757     } else {
11758         GameListDestroy();
11759         if (fseek(f, 0, 0) == -1) {
11760             if (f == lastLoadGameFP ?
11761                 gameNumber == lastLoadGameNumber + 1 :
11762                 gameNumber == 1) {
11763                 gn = 1;
11764             } else {
11765                 DisplayError(_("Can't seek on game file"), 0);
11766                 return FALSE;
11767             }
11768         }
11769     }
11770     lastLoadGameFP = f;
11771     lastLoadGameNumber = gameNumber;
11772     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11773     lastLoadGameUseList = useList;
11774
11775     yynewfile(f);
11776
11777     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11778       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11779                 lg->gameInfo.black);
11780             DisplayTitle(buf);
11781     } else if (*title != NULLCHAR) {
11782         if (gameNumber > 1) {
11783           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11784             DisplayTitle(buf);
11785         } else {
11786             DisplayTitle(title);
11787         }
11788     }
11789
11790     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11791         gameMode = PlayFromGameFile;
11792         ModeHighlight();
11793     }
11794
11795     currentMove = forwardMostMove = backwardMostMove = 0;
11796     CopyBoard(boards[0], initialPosition);
11797     StopClocks();
11798
11799     /*
11800      * Skip the first gn-1 games in the file.
11801      * Also skip over anything that precedes an identifiable
11802      * start of game marker, to avoid being confused by
11803      * garbage at the start of the file.  Currently
11804      * recognized start of game markers are the move number "1",
11805      * the pattern "gnuchess .* game", the pattern
11806      * "^[#;%] [^ ]* game file", and a PGN tag block.
11807      * A game that starts with one of the latter two patterns
11808      * will also have a move number 1, possibly
11809      * following a position diagram.
11810      * 5-4-02: Let's try being more lenient and allowing a game to
11811      * start with an unnumbered move.  Does that break anything?
11812      */
11813     cm = lastLoadGameStart = EndOfFile;
11814     while (gn > 0) {
11815         yyboardindex = forwardMostMove;
11816         cm = (ChessMove) Myylex();
11817         switch (cm) {
11818           case EndOfFile:
11819             if (cmailMsgLoaded) {
11820                 nCmailGames = CMAIL_MAX_GAMES - gn;
11821             } else {
11822                 Reset(TRUE, TRUE);
11823                 DisplayError(_("Game not found in file"), 0);
11824             }
11825             return FALSE;
11826
11827           case GNUChessGame:
11828           case XBoardGame:
11829             gn--;
11830             lastLoadGameStart = cm;
11831             break;
11832
11833           case MoveNumberOne:
11834             switch (lastLoadGameStart) {
11835               case GNUChessGame:
11836               case XBoardGame:
11837               case PGNTag:
11838                 break;
11839               case MoveNumberOne:
11840               case EndOfFile:
11841                 gn--;           /* count this game */
11842                 lastLoadGameStart = cm;
11843                 break;
11844               default:
11845                 /* impossible */
11846                 break;
11847             }
11848             break;
11849
11850           case PGNTag:
11851             switch (lastLoadGameStart) {
11852               case GNUChessGame:
11853               case PGNTag:
11854               case MoveNumberOne:
11855               case EndOfFile:
11856                 gn--;           /* count this game */
11857                 lastLoadGameStart = cm;
11858                 break;
11859               case XBoardGame:
11860                 lastLoadGameStart = cm; /* game counted already */
11861                 break;
11862               default:
11863                 /* impossible */
11864                 break;
11865             }
11866             if (gn > 0) {
11867                 do {
11868                     yyboardindex = forwardMostMove;
11869                     cm = (ChessMove) Myylex();
11870                 } while (cm == PGNTag || cm == Comment);
11871             }
11872             break;
11873
11874           case WhiteWins:
11875           case BlackWins:
11876           case GameIsDrawn:
11877             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11878                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11879                     != CMAIL_OLD_RESULT) {
11880                     nCmailResults ++ ;
11881                     cmailResult[  CMAIL_MAX_GAMES
11882                                 - gn - 1] = CMAIL_OLD_RESULT;
11883                 }
11884             }
11885             break;
11886
11887           case NormalMove:
11888             /* Only a NormalMove can be at the start of a game
11889              * without a position diagram. */
11890             if (lastLoadGameStart == EndOfFile ) {
11891               gn--;
11892               lastLoadGameStart = MoveNumberOne;
11893             }
11894             break;
11895
11896           default:
11897             break;
11898         }
11899     }
11900
11901     if (appData.debugMode)
11902       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11903
11904     if (cm == XBoardGame) {
11905         /* Skip any header junk before position diagram and/or move 1 */
11906         for (;;) {
11907             yyboardindex = forwardMostMove;
11908             cm = (ChessMove) Myylex();
11909
11910             if (cm == EndOfFile ||
11911                 cm == GNUChessGame || cm == XBoardGame) {
11912                 /* Empty game; pretend end-of-file and handle later */
11913                 cm = EndOfFile;
11914                 break;
11915             }
11916
11917             if (cm == MoveNumberOne || cm == PositionDiagram ||
11918                 cm == PGNTag || cm == Comment)
11919               break;
11920         }
11921     } else if (cm == GNUChessGame) {
11922         if (gameInfo.event != NULL) {
11923             free(gameInfo.event);
11924         }
11925         gameInfo.event = StrSave(yy_text);
11926     }
11927
11928     startedFromSetupPosition = FALSE;
11929     while (cm == PGNTag) {
11930         if (appData.debugMode)
11931           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11932         err = ParsePGNTag(yy_text, &gameInfo);
11933         if (!err) numPGNTags++;
11934
11935         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11936         if(gameInfo.variant != oldVariant) {
11937             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11938             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11939             InitPosition(TRUE);
11940             oldVariant = gameInfo.variant;
11941             if (appData.debugMode)
11942               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11943         }
11944
11945
11946         if (gameInfo.fen != NULL) {
11947           Board initial_position;
11948           startedFromSetupPosition = TRUE;
11949           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11950             Reset(TRUE, TRUE);
11951             DisplayError(_("Bad FEN position in file"), 0);
11952             return FALSE;
11953           }
11954           CopyBoard(boards[0], initial_position);
11955           if (blackPlaysFirst) {
11956             currentMove = forwardMostMove = backwardMostMove = 1;
11957             CopyBoard(boards[1], initial_position);
11958             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11959             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11960             timeRemaining[0][1] = whiteTimeRemaining;
11961             timeRemaining[1][1] = blackTimeRemaining;
11962             if (commentList[0] != NULL) {
11963               commentList[1] = commentList[0];
11964               commentList[0] = NULL;
11965             }
11966           } else {
11967             currentMove = forwardMostMove = backwardMostMove = 0;
11968           }
11969           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11970           {   int i;
11971               initialRulePlies = FENrulePlies;
11972               for( i=0; i< nrCastlingRights; i++ )
11973                   initialRights[i] = initial_position[CASTLING][i];
11974           }
11975           yyboardindex = forwardMostMove;
11976           free(gameInfo.fen);
11977           gameInfo.fen = NULL;
11978         }
11979
11980         yyboardindex = forwardMostMove;
11981         cm = (ChessMove) Myylex();
11982
11983         /* Handle comments interspersed among the tags */
11984         while (cm == Comment) {
11985             char *p;
11986             if (appData.debugMode)
11987               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11988             p = yy_text;
11989             AppendComment(currentMove, p, FALSE);
11990             yyboardindex = forwardMostMove;
11991             cm = (ChessMove) Myylex();
11992         }
11993     }
11994
11995     /* don't rely on existence of Event tag since if game was
11996      * pasted from clipboard the Event tag may not exist
11997      */
11998     if (numPGNTags > 0){
11999         char *tags;
12000         if (gameInfo.variant == VariantNormal) {
12001           VariantClass v = StringToVariant(gameInfo.event);
12002           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12003           if(v < VariantShogi) gameInfo.variant = v;
12004         }
12005         if (!matchMode) {
12006           if( appData.autoDisplayTags ) {
12007             tags = PGNTags(&gameInfo);
12008             TagsPopUp(tags, CmailMsg());
12009             free(tags);
12010           }
12011         }
12012     } else {
12013         /* Make something up, but don't display it now */
12014         SetGameInfo();
12015         TagsPopDown();
12016     }
12017
12018     if (cm == PositionDiagram) {
12019         int i, j;
12020         char *p;
12021         Board initial_position;
12022
12023         if (appData.debugMode)
12024           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12025
12026         if (!startedFromSetupPosition) {
12027             p = yy_text;
12028             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12029               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12030                 switch (*p) {
12031                   case '{':
12032                   case '[':
12033                   case '-':
12034                   case ' ':
12035                   case '\t':
12036                   case '\n':
12037                   case '\r':
12038                     break;
12039                   default:
12040                     initial_position[i][j++] = CharToPiece(*p);
12041                     break;
12042                 }
12043             while (*p == ' ' || *p == '\t' ||
12044                    *p == '\n' || *p == '\r') p++;
12045
12046             if (strncmp(p, "black", strlen("black"))==0)
12047               blackPlaysFirst = TRUE;
12048             else
12049               blackPlaysFirst = FALSE;
12050             startedFromSetupPosition = TRUE;
12051
12052             CopyBoard(boards[0], initial_position);
12053             if (blackPlaysFirst) {
12054                 currentMove = forwardMostMove = backwardMostMove = 1;
12055                 CopyBoard(boards[1], initial_position);
12056                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12057                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12058                 timeRemaining[0][1] = whiteTimeRemaining;
12059                 timeRemaining[1][1] = blackTimeRemaining;
12060                 if (commentList[0] != NULL) {
12061                     commentList[1] = commentList[0];
12062                     commentList[0] = NULL;
12063                 }
12064             } else {
12065                 currentMove = forwardMostMove = backwardMostMove = 0;
12066             }
12067         }
12068         yyboardindex = forwardMostMove;
12069         cm = (ChessMove) Myylex();
12070     }
12071
12072     if (first.pr == NoProc) {
12073         StartChessProgram(&first);
12074     }
12075     InitChessProgram(&first, FALSE);
12076     SendToProgram("force\n", &first);
12077     if (startedFromSetupPosition) {
12078         SendBoard(&first, forwardMostMove);
12079     if (appData.debugMode) {
12080         fprintf(debugFP, "Load Game\n");
12081     }
12082         DisplayBothClocks();
12083     }
12084
12085     /* [HGM] server: flag to write setup moves in broadcast file as one */
12086     loadFlag = appData.suppressLoadMoves;
12087
12088     while (cm == Comment) {
12089         char *p;
12090         if (appData.debugMode)
12091           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12092         p = yy_text;
12093         AppendComment(currentMove, p, FALSE);
12094         yyboardindex = forwardMostMove;
12095         cm = (ChessMove) Myylex();
12096     }
12097
12098     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12099         cm == WhiteWins || cm == BlackWins ||
12100         cm == GameIsDrawn || cm == GameUnfinished) {
12101         DisplayMessage("", _("No moves in game"));
12102         if (cmailMsgLoaded) {
12103             if (appData.debugMode)
12104               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12105             ClearHighlights();
12106             flipView = FALSE;
12107         }
12108         DrawPosition(FALSE, boards[currentMove]);
12109         DisplayBothClocks();
12110         gameMode = EditGame;
12111         ModeHighlight();
12112         gameFileFP = NULL;
12113         cmailOldMove = 0;
12114         return TRUE;
12115     }
12116
12117     // [HGM] PV info: routine tests if comment empty
12118     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12119         DisplayComment(currentMove - 1, commentList[currentMove]);
12120     }
12121     if (!matchMode && appData.timeDelay != 0)
12122       DrawPosition(FALSE, boards[currentMove]);
12123
12124     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12125       programStats.ok_to_send = 1;
12126     }
12127
12128     /* if the first token after the PGN tags is a move
12129      * and not move number 1, retrieve it from the parser
12130      */
12131     if (cm != MoveNumberOne)
12132         LoadGameOneMove(cm);
12133
12134     /* load the remaining moves from the file */
12135     while (LoadGameOneMove(EndOfFile)) {
12136       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12137       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12138     }
12139
12140     /* rewind to the start of the game */
12141     currentMove = backwardMostMove;
12142
12143     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12144
12145     if (oldGameMode == AnalyzeFile ||
12146         oldGameMode == AnalyzeMode) {
12147       AnalyzeFileEvent();
12148     }
12149
12150     if (!matchMode && pos >= 0) {
12151         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12152     } else
12153     if (matchMode || appData.timeDelay == 0) {
12154       ToEndEvent();
12155     } else if (appData.timeDelay > 0) {
12156       AutoPlayGameLoop();
12157     }
12158
12159     if (appData.debugMode)
12160         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12161
12162     loadFlag = 0; /* [HGM] true game starts */
12163     return TRUE;
12164 }
12165
12166 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12167 int
12168 ReloadPosition (int offset)
12169 {
12170     int positionNumber = lastLoadPositionNumber + offset;
12171     if (lastLoadPositionFP == NULL) {
12172         DisplayError(_("No position has been loaded yet"), 0);
12173         return FALSE;
12174     }
12175     if (positionNumber <= 0) {
12176         DisplayError(_("Can't back up any further"), 0);
12177         return FALSE;
12178     }
12179     return LoadPosition(lastLoadPositionFP, positionNumber,
12180                         lastLoadPositionTitle);
12181 }
12182
12183 /* Load the nth position from the given file */
12184 int
12185 LoadPositionFromFile (char *filename, int n, char *title)
12186 {
12187     FILE *f;
12188     char buf[MSG_SIZ];
12189
12190     if (strcmp(filename, "-") == 0) {
12191         return LoadPosition(stdin, n, "stdin");
12192     } else {
12193         f = fopen(filename, "rb");
12194         if (f == NULL) {
12195             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12196             DisplayError(buf, errno);
12197             return FALSE;
12198         } else {
12199             return LoadPosition(f, n, title);
12200         }
12201     }
12202 }
12203
12204 /* Load the nth position from the given open file, and close it */
12205 int
12206 LoadPosition (FILE *f, int positionNumber, char *title)
12207 {
12208     char *p, line[MSG_SIZ];
12209     Board initial_position;
12210     int i, j, fenMode, pn;
12211
12212     if (gameMode == Training )
12213         SetTrainingModeOff();
12214
12215     if (gameMode != BeginningOfGame) {
12216         Reset(FALSE, TRUE);
12217     }
12218     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12219         fclose(lastLoadPositionFP);
12220     }
12221     if (positionNumber == 0) positionNumber = 1;
12222     lastLoadPositionFP = f;
12223     lastLoadPositionNumber = positionNumber;
12224     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12225     if (first.pr == NoProc && !appData.noChessProgram) {
12226       StartChessProgram(&first);
12227       InitChessProgram(&first, FALSE);
12228     }
12229     pn = positionNumber;
12230     if (positionNumber < 0) {
12231         /* Negative position number means to seek to that byte offset */
12232         if (fseek(f, -positionNumber, 0) == -1) {
12233             DisplayError(_("Can't seek on position file"), 0);
12234             return FALSE;
12235         };
12236         pn = 1;
12237     } else {
12238         if (fseek(f, 0, 0) == -1) {
12239             if (f == lastLoadPositionFP ?
12240                 positionNumber == lastLoadPositionNumber + 1 :
12241                 positionNumber == 1) {
12242                 pn = 1;
12243             } else {
12244                 DisplayError(_("Can't seek on position file"), 0);
12245                 return FALSE;
12246             }
12247         }
12248     }
12249     /* See if this file is FEN or old-style xboard */
12250     if (fgets(line, MSG_SIZ, f) == NULL) {
12251         DisplayError(_("Position not found in file"), 0);
12252         return FALSE;
12253     }
12254     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12255     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12256
12257     if (pn >= 2) {
12258         if (fenMode || line[0] == '#') pn--;
12259         while (pn > 0) {
12260             /* skip positions before number pn */
12261             if (fgets(line, MSG_SIZ, f) == NULL) {
12262                 Reset(TRUE, TRUE);
12263                 DisplayError(_("Position not found in file"), 0);
12264                 return FALSE;
12265             }
12266             if (fenMode || line[0] == '#') pn--;
12267         }
12268     }
12269
12270     if (fenMode) {
12271         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12272             DisplayError(_("Bad FEN position in file"), 0);
12273             return FALSE;
12274         }
12275     } else {
12276         (void) fgets(line, MSG_SIZ, f);
12277         (void) fgets(line, MSG_SIZ, f);
12278
12279         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12280             (void) fgets(line, MSG_SIZ, f);
12281             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12282                 if (*p == ' ')
12283                   continue;
12284                 initial_position[i][j++] = CharToPiece(*p);
12285             }
12286         }
12287
12288         blackPlaysFirst = FALSE;
12289         if (!feof(f)) {
12290             (void) fgets(line, MSG_SIZ, f);
12291             if (strncmp(line, "black", strlen("black"))==0)
12292               blackPlaysFirst = TRUE;
12293         }
12294     }
12295     startedFromSetupPosition = TRUE;
12296
12297     CopyBoard(boards[0], initial_position);
12298     if (blackPlaysFirst) {
12299         currentMove = forwardMostMove = backwardMostMove = 1;
12300         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12301         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12302         CopyBoard(boards[1], initial_position);
12303         DisplayMessage("", _("Black to play"));
12304     } else {
12305         currentMove = forwardMostMove = backwardMostMove = 0;
12306         DisplayMessage("", _("White to play"));
12307     }
12308     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12309     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12310         SendToProgram("force\n", &first);
12311         SendBoard(&first, forwardMostMove);
12312     }
12313     if (appData.debugMode) {
12314 int i, j;
12315   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12316   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12317         fprintf(debugFP, "Load Position\n");
12318     }
12319
12320     if (positionNumber > 1) {
12321       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12322         DisplayTitle(line);
12323     } else {
12324         DisplayTitle(title);
12325     }
12326     gameMode = EditGame;
12327     ModeHighlight();
12328     ResetClocks();
12329     timeRemaining[0][1] = whiteTimeRemaining;
12330     timeRemaining[1][1] = blackTimeRemaining;
12331     DrawPosition(FALSE, boards[currentMove]);
12332
12333     return TRUE;
12334 }
12335
12336
12337 void
12338 CopyPlayerNameIntoFileName (char **dest, char *src)
12339 {
12340     while (*src != NULLCHAR && *src != ',') {
12341         if (*src == ' ') {
12342             *(*dest)++ = '_';
12343             src++;
12344         } else {
12345             *(*dest)++ = *src++;
12346         }
12347     }
12348 }
12349
12350 char *
12351 DefaultFileName (char *ext)
12352 {
12353     static char def[MSG_SIZ];
12354     char *p;
12355
12356     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12357         p = def;
12358         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12359         *p++ = '-';
12360         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12361         *p++ = '.';
12362         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12363     } else {
12364         def[0] = NULLCHAR;
12365     }
12366     return def;
12367 }
12368
12369 /* Save the current game to the given file */
12370 int
12371 SaveGameToFile (char *filename, int append)
12372 {
12373     FILE *f;
12374     char buf[MSG_SIZ];
12375     int result, i, t,tot=0;
12376
12377     if (strcmp(filename, "-") == 0) {
12378         return SaveGame(stdout, 0, NULL);
12379     } else {
12380         for(i=0; i<10; i++) { // upto 10 tries
12381              f = fopen(filename, append ? "a" : "w");
12382              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12383              if(f || errno != 13) break;
12384              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12385              tot += t;
12386         }
12387         if (f == NULL) {
12388             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12389             DisplayError(buf, errno);
12390             return FALSE;
12391         } else {
12392             safeStrCpy(buf, lastMsg, MSG_SIZ);
12393             DisplayMessage(_("Waiting for access to save file"), "");
12394             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12395             DisplayMessage(_("Saving game"), "");
12396             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12397             result = SaveGame(f, 0, NULL);
12398             DisplayMessage(buf, "");
12399             return result;
12400         }
12401     }
12402 }
12403
12404 char *
12405 SavePart (char *str)
12406 {
12407     static char buf[MSG_SIZ];
12408     char *p;
12409
12410     p = strchr(str, ' ');
12411     if (p == NULL) return str;
12412     strncpy(buf, str, p - str);
12413     buf[p - str] = NULLCHAR;
12414     return buf;
12415 }
12416
12417 #define PGN_MAX_LINE 75
12418
12419 #define PGN_SIDE_WHITE  0
12420 #define PGN_SIDE_BLACK  1
12421
12422 static int
12423 FindFirstMoveOutOfBook (int side)
12424 {
12425     int result = -1;
12426
12427     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12428         int index = backwardMostMove;
12429         int has_book_hit = 0;
12430
12431         if( (index % 2) != side ) {
12432             index++;
12433         }
12434
12435         while( index < forwardMostMove ) {
12436             /* Check to see if engine is in book */
12437             int depth = pvInfoList[index].depth;
12438             int score = pvInfoList[index].score;
12439             int in_book = 0;
12440
12441             if( depth <= 2 ) {
12442                 in_book = 1;
12443             }
12444             else if( score == 0 && depth == 63 ) {
12445                 in_book = 1; /* Zappa */
12446             }
12447             else if( score == 2 && depth == 99 ) {
12448                 in_book = 1; /* Abrok */
12449             }
12450
12451             has_book_hit += in_book;
12452
12453             if( ! in_book ) {
12454                 result = index;
12455
12456                 break;
12457             }
12458
12459             index += 2;
12460         }
12461     }
12462
12463     return result;
12464 }
12465
12466 void
12467 GetOutOfBookInfo (char * buf)
12468 {
12469     int oob[2];
12470     int i;
12471     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12472
12473     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12474     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12475
12476     *buf = '\0';
12477
12478     if( oob[0] >= 0 || oob[1] >= 0 ) {
12479         for( i=0; i<2; i++ ) {
12480             int idx = oob[i];
12481
12482             if( idx >= 0 ) {
12483                 if( i > 0 && oob[0] >= 0 ) {
12484                     strcat( buf, "   " );
12485                 }
12486
12487                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12488                 sprintf( buf+strlen(buf), "%s%.2f",
12489                     pvInfoList[idx].score >= 0 ? "+" : "",
12490                     pvInfoList[idx].score / 100.0 );
12491             }
12492         }
12493     }
12494 }
12495
12496 /* Save game in PGN style and close the file */
12497 int
12498 SaveGamePGN (FILE *f)
12499 {
12500     int i, offset, linelen, newblock;
12501     time_t tm;
12502 //    char *movetext;
12503     char numtext[32];
12504     int movelen, numlen, blank;
12505     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12506
12507     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12508
12509     tm = time((time_t *) NULL);
12510
12511     PrintPGNTags(f, &gameInfo);
12512
12513     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12514
12515     if (backwardMostMove > 0 || startedFromSetupPosition) {
12516         char *fen = PositionToFEN(backwardMostMove, NULL);
12517         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12518         fprintf(f, "\n{--------------\n");
12519         PrintPosition(f, backwardMostMove);
12520         fprintf(f, "--------------}\n");
12521         free(fen);
12522     }
12523     else {
12524         /* [AS] Out of book annotation */
12525         if( appData.saveOutOfBookInfo ) {
12526             char buf[64];
12527
12528             GetOutOfBookInfo( buf );
12529
12530             if( buf[0] != '\0' ) {
12531                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12532             }
12533         }
12534
12535         fprintf(f, "\n");
12536     }
12537
12538     i = backwardMostMove;
12539     linelen = 0;
12540     newblock = TRUE;
12541
12542     while (i < forwardMostMove) {
12543         /* Print comments preceding this move */
12544         if (commentList[i] != NULL) {
12545             if (linelen > 0) fprintf(f, "\n");
12546             fprintf(f, "%s", commentList[i]);
12547             linelen = 0;
12548             newblock = TRUE;
12549         }
12550
12551         /* Format move number */
12552         if ((i % 2) == 0)
12553           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12554         else
12555           if (newblock)
12556             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12557           else
12558             numtext[0] = NULLCHAR;
12559
12560         numlen = strlen(numtext);
12561         newblock = FALSE;
12562
12563         /* Print move number */
12564         blank = linelen > 0 && numlen > 0;
12565         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12566             fprintf(f, "\n");
12567             linelen = 0;
12568             blank = 0;
12569         }
12570         if (blank) {
12571             fprintf(f, " ");
12572             linelen++;
12573         }
12574         fprintf(f, "%s", numtext);
12575         linelen += numlen;
12576
12577         /* Get move */
12578         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12579         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12580
12581         /* Print move */
12582         blank = linelen > 0 && movelen > 0;
12583         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12584             fprintf(f, "\n");
12585             linelen = 0;
12586             blank = 0;
12587         }
12588         if (blank) {
12589             fprintf(f, " ");
12590             linelen++;
12591         }
12592         fprintf(f, "%s", move_buffer);
12593         linelen += movelen;
12594
12595         /* [AS] Add PV info if present */
12596         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12597             /* [HGM] add time */
12598             char buf[MSG_SIZ]; int seconds;
12599
12600             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12601
12602             if( seconds <= 0)
12603               buf[0] = 0;
12604             else
12605               if( seconds < 30 )
12606                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12607               else
12608                 {
12609                   seconds = (seconds + 4)/10; // round to full seconds
12610                   if( seconds < 60 )
12611                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12612                   else
12613                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12614                 }
12615
12616             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12617                       pvInfoList[i].score >= 0 ? "+" : "",
12618                       pvInfoList[i].score / 100.0,
12619                       pvInfoList[i].depth,
12620                       buf );
12621
12622             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12623
12624             /* Print score/depth */
12625             blank = linelen > 0 && movelen > 0;
12626             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12627                 fprintf(f, "\n");
12628                 linelen = 0;
12629                 blank = 0;
12630             }
12631             if (blank) {
12632                 fprintf(f, " ");
12633                 linelen++;
12634             }
12635             fprintf(f, "%s", move_buffer);
12636             linelen += movelen;
12637         }
12638
12639         i++;
12640     }
12641
12642     /* Start a new line */
12643     if (linelen > 0) fprintf(f, "\n");
12644
12645     /* Print comments after last move */
12646     if (commentList[i] != NULL) {
12647         fprintf(f, "%s\n", commentList[i]);
12648     }
12649
12650     /* Print result */
12651     if (gameInfo.resultDetails != NULL &&
12652         gameInfo.resultDetails[0] != NULLCHAR) {
12653         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12654                 PGNResult(gameInfo.result));
12655     } else {
12656         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12657     }
12658
12659     fclose(f);
12660     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12661     return TRUE;
12662 }
12663
12664 /* Save game in old style and close the file */
12665 int
12666 SaveGameOldStyle (FILE *f)
12667 {
12668     int i, offset;
12669     time_t tm;
12670
12671     tm = time((time_t *) NULL);
12672
12673     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12674     PrintOpponents(f);
12675
12676     if (backwardMostMove > 0 || startedFromSetupPosition) {
12677         fprintf(f, "\n[--------------\n");
12678         PrintPosition(f, backwardMostMove);
12679         fprintf(f, "--------------]\n");
12680     } else {
12681         fprintf(f, "\n");
12682     }
12683
12684     i = backwardMostMove;
12685     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12686
12687     while (i < forwardMostMove) {
12688         if (commentList[i] != NULL) {
12689             fprintf(f, "[%s]\n", commentList[i]);
12690         }
12691
12692         if ((i % 2) == 1) {
12693             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12694             i++;
12695         } else {
12696             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12697             i++;
12698             if (commentList[i] != NULL) {
12699                 fprintf(f, "\n");
12700                 continue;
12701             }
12702             if (i >= forwardMostMove) {
12703                 fprintf(f, "\n");
12704                 break;
12705             }
12706             fprintf(f, "%s\n", parseList[i]);
12707             i++;
12708         }
12709     }
12710
12711     if (commentList[i] != NULL) {
12712         fprintf(f, "[%s]\n", commentList[i]);
12713     }
12714
12715     /* This isn't really the old style, but it's close enough */
12716     if (gameInfo.resultDetails != NULL &&
12717         gameInfo.resultDetails[0] != NULLCHAR) {
12718         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12719                 gameInfo.resultDetails);
12720     } else {
12721         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12722     }
12723
12724     fclose(f);
12725     return TRUE;
12726 }
12727
12728 /* Save the current game to open file f and close the file */
12729 int
12730 SaveGame (FILE *f, int dummy, char *dummy2)
12731 {
12732     if (gameMode == EditPosition) EditPositionDone(TRUE);
12733     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12734     if (appData.oldSaveStyle)
12735       return SaveGameOldStyle(f);
12736     else
12737       return SaveGamePGN(f);
12738 }
12739
12740 /* Save the current position to the given file */
12741 int
12742 SavePositionToFile (char *filename)
12743 {
12744     FILE *f;
12745     char buf[MSG_SIZ];
12746
12747     if (strcmp(filename, "-") == 0) {
12748         return SavePosition(stdout, 0, NULL);
12749     } else {
12750         f = fopen(filename, "a");
12751         if (f == NULL) {
12752             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12753             DisplayError(buf, errno);
12754             return FALSE;
12755         } else {
12756             safeStrCpy(buf, lastMsg, MSG_SIZ);
12757             DisplayMessage(_("Waiting for access to save file"), "");
12758             flock(fileno(f), LOCK_EX); // [HGM] lock
12759             DisplayMessage(_("Saving position"), "");
12760             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12761             SavePosition(f, 0, NULL);
12762             DisplayMessage(buf, "");
12763             return TRUE;
12764         }
12765     }
12766 }
12767
12768 /* Save the current position to the given open file and close the file */
12769 int
12770 SavePosition (FILE *f, int dummy, char *dummy2)
12771 {
12772     time_t tm;
12773     char *fen;
12774
12775     if (gameMode == EditPosition) EditPositionDone(TRUE);
12776     if (appData.oldSaveStyle) {
12777         tm = time((time_t *) NULL);
12778
12779         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12780         PrintOpponents(f);
12781         fprintf(f, "[--------------\n");
12782         PrintPosition(f, currentMove);
12783         fprintf(f, "--------------]\n");
12784     } else {
12785         fen = PositionToFEN(currentMove, NULL);
12786         fprintf(f, "%s\n", fen);
12787         free(fen);
12788     }
12789     fclose(f);
12790     return TRUE;
12791 }
12792
12793 void
12794 ReloadCmailMsgEvent (int unregister)
12795 {
12796 #if !WIN32
12797     static char *inFilename = NULL;
12798     static char *outFilename;
12799     int i;
12800     struct stat inbuf, outbuf;
12801     int status;
12802
12803     /* Any registered moves are unregistered if unregister is set, */
12804     /* i.e. invoked by the signal handler */
12805     if (unregister) {
12806         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12807             cmailMoveRegistered[i] = FALSE;
12808             if (cmailCommentList[i] != NULL) {
12809                 free(cmailCommentList[i]);
12810                 cmailCommentList[i] = NULL;
12811             }
12812         }
12813         nCmailMovesRegistered = 0;
12814     }
12815
12816     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12817         cmailResult[i] = CMAIL_NOT_RESULT;
12818     }
12819     nCmailResults = 0;
12820
12821     if (inFilename == NULL) {
12822         /* Because the filenames are static they only get malloced once  */
12823         /* and they never get freed                                      */
12824         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12825         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12826
12827         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12828         sprintf(outFilename, "%s.out", appData.cmailGameName);
12829     }
12830
12831     status = stat(outFilename, &outbuf);
12832     if (status < 0) {
12833         cmailMailedMove = FALSE;
12834     } else {
12835         status = stat(inFilename, &inbuf);
12836         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12837     }
12838
12839     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12840        counts the games, notes how each one terminated, etc.
12841
12842        It would be nice to remove this kludge and instead gather all
12843        the information while building the game list.  (And to keep it
12844        in the game list nodes instead of having a bunch of fixed-size
12845        parallel arrays.)  Note this will require getting each game's
12846        termination from the PGN tags, as the game list builder does
12847        not process the game moves.  --mann
12848        */
12849     cmailMsgLoaded = TRUE;
12850     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12851
12852     /* Load first game in the file or popup game menu */
12853     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12854
12855 #endif /* !WIN32 */
12856     return;
12857 }
12858
12859 int
12860 RegisterMove ()
12861 {
12862     FILE *f;
12863     char string[MSG_SIZ];
12864
12865     if (   cmailMailedMove
12866         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12867         return TRUE;            /* Allow free viewing  */
12868     }
12869
12870     /* Unregister move to ensure that we don't leave RegisterMove        */
12871     /* with the move registered when the conditions for registering no   */
12872     /* longer hold                                                       */
12873     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12874         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12875         nCmailMovesRegistered --;
12876
12877         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12878           {
12879               free(cmailCommentList[lastLoadGameNumber - 1]);
12880               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12881           }
12882     }
12883
12884     if (cmailOldMove == -1) {
12885         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12886         return FALSE;
12887     }
12888
12889     if (currentMove > cmailOldMove + 1) {
12890         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12891         return FALSE;
12892     }
12893
12894     if (currentMove < cmailOldMove) {
12895         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12896         return FALSE;
12897     }
12898
12899     if (forwardMostMove > currentMove) {
12900         /* Silently truncate extra moves */
12901         TruncateGame();
12902     }
12903
12904     if (   (currentMove == cmailOldMove + 1)
12905         || (   (currentMove == cmailOldMove)
12906             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12907                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12908         if (gameInfo.result != GameUnfinished) {
12909             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12910         }
12911
12912         if (commentList[currentMove] != NULL) {
12913             cmailCommentList[lastLoadGameNumber - 1]
12914               = StrSave(commentList[currentMove]);
12915         }
12916         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12917
12918         if (appData.debugMode)
12919           fprintf(debugFP, "Saving %s for game %d\n",
12920                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12921
12922         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12923
12924         f = fopen(string, "w");
12925         if (appData.oldSaveStyle) {
12926             SaveGameOldStyle(f); /* also closes the file */
12927
12928             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12929             f = fopen(string, "w");
12930             SavePosition(f, 0, NULL); /* also closes the file */
12931         } else {
12932             fprintf(f, "{--------------\n");
12933             PrintPosition(f, currentMove);
12934             fprintf(f, "--------------}\n\n");
12935
12936             SaveGame(f, 0, NULL); /* also closes the file*/
12937         }
12938
12939         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12940         nCmailMovesRegistered ++;
12941     } else if (nCmailGames == 1) {
12942         DisplayError(_("You have not made a move yet"), 0);
12943         return FALSE;
12944     }
12945
12946     return TRUE;
12947 }
12948
12949 void
12950 MailMoveEvent ()
12951 {
12952 #if !WIN32
12953     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12954     FILE *commandOutput;
12955     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12956     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12957     int nBuffers;
12958     int i;
12959     int archived;
12960     char *arcDir;
12961
12962     if (! cmailMsgLoaded) {
12963         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12964         return;
12965     }
12966
12967     if (nCmailGames == nCmailResults) {
12968         DisplayError(_("No unfinished games"), 0);
12969         return;
12970     }
12971
12972 #if CMAIL_PROHIBIT_REMAIL
12973     if (cmailMailedMove) {
12974       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);
12975         DisplayError(msg, 0);
12976         return;
12977     }
12978 #endif
12979
12980     if (! (cmailMailedMove || RegisterMove())) return;
12981
12982     if (   cmailMailedMove
12983         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12984       snprintf(string, MSG_SIZ, partCommandString,
12985                appData.debugMode ? " -v" : "", appData.cmailGameName);
12986         commandOutput = popen(string, "r");
12987
12988         if (commandOutput == NULL) {
12989             DisplayError(_("Failed to invoke cmail"), 0);
12990         } else {
12991             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12992                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12993             }
12994             if (nBuffers > 1) {
12995                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12996                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12997                 nBytes = MSG_SIZ - 1;
12998             } else {
12999                 (void) memcpy(msg, buffer, nBytes);
13000             }
13001             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13002
13003             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13004                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13005
13006                 archived = TRUE;
13007                 for (i = 0; i < nCmailGames; i ++) {
13008                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13009                         archived = FALSE;
13010                     }
13011                 }
13012                 if (   archived
13013                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13014                         != NULL)) {
13015                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13016                            arcDir,
13017                            appData.cmailGameName,
13018                            gameInfo.date);
13019                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13020                     cmailMsgLoaded = FALSE;
13021                 }
13022             }
13023
13024             DisplayInformation(msg);
13025             pclose(commandOutput);
13026         }
13027     } else {
13028         if ((*cmailMsg) != '\0') {
13029             DisplayInformation(cmailMsg);
13030         }
13031     }
13032
13033     return;
13034 #endif /* !WIN32 */
13035 }
13036
13037 char *
13038 CmailMsg ()
13039 {
13040 #if WIN32
13041     return NULL;
13042 #else
13043     int  prependComma = 0;
13044     char number[5];
13045     char string[MSG_SIZ];       /* Space for game-list */
13046     int  i;
13047
13048     if (!cmailMsgLoaded) return "";
13049
13050     if (cmailMailedMove) {
13051       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13052     } else {
13053         /* Create a list of games left */
13054       snprintf(string, MSG_SIZ, "[");
13055         for (i = 0; i < nCmailGames; i ++) {
13056             if (! (   cmailMoveRegistered[i]
13057                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13058                 if (prependComma) {
13059                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13060                 } else {
13061                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13062                     prependComma = 1;
13063                 }
13064
13065                 strcat(string, number);
13066             }
13067         }
13068         strcat(string, "]");
13069
13070         if (nCmailMovesRegistered + nCmailResults == 0) {
13071             switch (nCmailGames) {
13072               case 1:
13073                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13074                 break;
13075
13076               case 2:
13077                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13078                 break;
13079
13080               default:
13081                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13082                          nCmailGames);
13083                 break;
13084             }
13085         } else {
13086             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13087               case 1:
13088                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13089                          string);
13090                 break;
13091
13092               case 0:
13093                 if (nCmailResults == nCmailGames) {
13094                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13095                 } else {
13096                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13097                 }
13098                 break;
13099
13100               default:
13101                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13102                          string);
13103             }
13104         }
13105     }
13106     return cmailMsg;
13107 #endif /* WIN32 */
13108 }
13109
13110 void
13111 ResetGameEvent ()
13112 {
13113     if (gameMode == Training)
13114       SetTrainingModeOff();
13115
13116     Reset(TRUE, TRUE);
13117     cmailMsgLoaded = FALSE;
13118     if (appData.icsActive) {
13119       SendToICS(ics_prefix);
13120       SendToICS("refresh\n");
13121     }
13122 }
13123
13124 void
13125 ExitEvent (int status)
13126 {
13127     exiting++;
13128     if (exiting > 2) {
13129       /* Give up on clean exit */
13130       exit(status);
13131     }
13132     if (exiting > 1) {
13133       /* Keep trying for clean exit */
13134       return;
13135     }
13136
13137     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13138
13139     if (telnetISR != NULL) {
13140       RemoveInputSource(telnetISR);
13141     }
13142     if (icsPR != NoProc) {
13143       DestroyChildProcess(icsPR, TRUE);
13144     }
13145
13146     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13147     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13148
13149     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13150     /* make sure this other one finishes before killing it!                  */
13151     if(endingGame) { int count = 0;
13152         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13153         while(endingGame && count++ < 10) DoSleep(1);
13154         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13155     }
13156
13157     /* Kill off chess programs */
13158     if (first.pr != NoProc) {
13159         ExitAnalyzeMode();
13160
13161         DoSleep( appData.delayBeforeQuit );
13162         SendToProgram("quit\n", &first);
13163         DoSleep( appData.delayAfterQuit );
13164         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13165     }
13166     if (second.pr != NoProc) {
13167         DoSleep( appData.delayBeforeQuit );
13168         SendToProgram("quit\n", &second);
13169         DoSleep( appData.delayAfterQuit );
13170         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13171     }
13172     if (first.isr != NULL) {
13173         RemoveInputSource(first.isr);
13174     }
13175     if (second.isr != NULL) {
13176         RemoveInputSource(second.isr);
13177     }
13178
13179     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13180     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13181
13182     ShutDownFrontEnd();
13183     exit(status);
13184 }
13185
13186 void
13187 PauseEvent ()
13188 {
13189     if (appData.debugMode)
13190         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13191     if (pausing) {
13192         pausing = FALSE;
13193         ModeHighlight();
13194         if (gameMode == MachinePlaysWhite ||
13195             gameMode == MachinePlaysBlack) {
13196             StartClocks();
13197         } else {
13198             DisplayBothClocks();
13199         }
13200         if (gameMode == PlayFromGameFile) {
13201             if (appData.timeDelay >= 0)
13202                 AutoPlayGameLoop();
13203         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13204             Reset(FALSE, TRUE);
13205             SendToICS(ics_prefix);
13206             SendToICS("refresh\n");
13207         } else if (currentMove < forwardMostMove) {
13208             ForwardInner(forwardMostMove);
13209         }
13210         pauseExamInvalid = FALSE;
13211     } else {
13212         switch (gameMode) {
13213           default:
13214             return;
13215           case IcsExamining:
13216             pauseExamForwardMostMove = forwardMostMove;
13217             pauseExamInvalid = FALSE;
13218             /* fall through */
13219           case IcsObserving:
13220           case IcsPlayingWhite:
13221           case IcsPlayingBlack:
13222             pausing = TRUE;
13223             ModeHighlight();
13224             return;
13225           case PlayFromGameFile:
13226             (void) StopLoadGameTimer();
13227             pausing = TRUE;
13228             ModeHighlight();
13229             break;
13230           case BeginningOfGame:
13231             if (appData.icsActive) return;
13232             /* else fall through */
13233           case MachinePlaysWhite:
13234           case MachinePlaysBlack:
13235           case TwoMachinesPlay:
13236             if (forwardMostMove == 0)
13237               return;           /* don't pause if no one has moved */
13238             if ((gameMode == MachinePlaysWhite &&
13239                  !WhiteOnMove(forwardMostMove)) ||
13240                 (gameMode == MachinePlaysBlack &&
13241                  WhiteOnMove(forwardMostMove))) {
13242                 StopClocks();
13243             }
13244             pausing = TRUE;
13245             ModeHighlight();
13246             break;
13247         }
13248     }
13249 }
13250
13251 void
13252 EditCommentEvent ()
13253 {
13254     char title[MSG_SIZ];
13255
13256     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13257       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13258     } else {
13259       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13260                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13261                parseList[currentMove - 1]);
13262     }
13263
13264     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13265 }
13266
13267
13268 void
13269 EditTagsEvent ()
13270 {
13271     char *tags = PGNTags(&gameInfo);
13272     bookUp = FALSE;
13273     EditTagsPopUp(tags, NULL);
13274     free(tags);
13275 }
13276
13277 void
13278 AnalyzeModeEvent ()
13279 {
13280     if (appData.noChessProgram || gameMode == AnalyzeMode)
13281       return;
13282
13283     if (gameMode != AnalyzeFile) {
13284         if (!appData.icsEngineAnalyze) {
13285                EditGameEvent();
13286                if (gameMode != EditGame) return;
13287         }
13288         ResurrectChessProgram();
13289         SendToProgram("analyze\n", &first);
13290         first.analyzing = TRUE;
13291         /*first.maybeThinking = TRUE;*/
13292         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13293         EngineOutputPopUp();
13294     }
13295     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13296     pausing = FALSE;
13297     ModeHighlight();
13298     SetGameInfo();
13299
13300     StartAnalysisClock();
13301     GetTimeMark(&lastNodeCountTime);
13302     lastNodeCount = 0;
13303 }
13304
13305 void
13306 AnalyzeFileEvent ()
13307 {
13308     if (appData.noChessProgram || gameMode == AnalyzeFile)
13309       return;
13310
13311     if (gameMode != AnalyzeMode) {
13312         EditGameEvent();
13313         if (gameMode != EditGame) return;
13314         ResurrectChessProgram();
13315         SendToProgram("analyze\n", &first);
13316         first.analyzing = TRUE;
13317         /*first.maybeThinking = TRUE;*/
13318         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13319         EngineOutputPopUp();
13320     }
13321     gameMode = AnalyzeFile;
13322     pausing = FALSE;
13323     ModeHighlight();
13324     SetGameInfo();
13325
13326     StartAnalysisClock();
13327     GetTimeMark(&lastNodeCountTime);
13328     lastNodeCount = 0;
13329     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13330 }
13331
13332 void
13333 MachineWhiteEvent ()
13334 {
13335     char buf[MSG_SIZ];
13336     char *bookHit = NULL;
13337
13338     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13339       return;
13340
13341
13342     if (gameMode == PlayFromGameFile ||
13343         gameMode == TwoMachinesPlay  ||
13344         gameMode == Training         ||
13345         gameMode == AnalyzeMode      ||
13346         gameMode == EndOfGame)
13347         EditGameEvent();
13348
13349     if (gameMode == EditPosition)
13350         EditPositionDone(TRUE);
13351
13352     if (!WhiteOnMove(currentMove)) {
13353         DisplayError(_("It is not White's turn"), 0);
13354         return;
13355     }
13356
13357     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13358       ExitAnalyzeMode();
13359
13360     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13361         gameMode == AnalyzeFile)
13362         TruncateGame();
13363
13364     ResurrectChessProgram();    /* in case it isn't running */
13365     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13366         gameMode = MachinePlaysWhite;
13367         ResetClocks();
13368     } else
13369     gameMode = MachinePlaysWhite;
13370     pausing = FALSE;
13371     ModeHighlight();
13372     SetGameInfo();
13373     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13374     DisplayTitle(buf);
13375     if (first.sendName) {
13376       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13377       SendToProgram(buf, &first);
13378     }
13379     if (first.sendTime) {
13380       if (first.useColors) {
13381         SendToProgram("black\n", &first); /*gnu kludge*/
13382       }
13383       SendTimeRemaining(&first, TRUE);
13384     }
13385     if (first.useColors) {
13386       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13387     }
13388     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13389     SetMachineThinkingEnables();
13390     first.maybeThinking = TRUE;
13391     StartClocks();
13392     firstMove = FALSE;
13393
13394     if (appData.autoFlipView && !flipView) {
13395       flipView = !flipView;
13396       DrawPosition(FALSE, NULL);
13397       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13398     }
13399
13400     if(bookHit) { // [HGM] book: simulate book reply
13401         static char bookMove[MSG_SIZ]; // a bit generous?
13402
13403         programStats.nodes = programStats.depth = programStats.time =
13404         programStats.score = programStats.got_only_move = 0;
13405         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13406
13407         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13408         strcat(bookMove, bookHit);
13409         HandleMachineMove(bookMove, &first);
13410     }
13411 }
13412
13413 void
13414 MachineBlackEvent ()
13415 {
13416   char buf[MSG_SIZ];
13417   char *bookHit = NULL;
13418
13419     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13420         return;
13421
13422
13423     if (gameMode == PlayFromGameFile ||
13424         gameMode == TwoMachinesPlay  ||
13425         gameMode == Training         ||
13426         gameMode == AnalyzeMode      ||
13427         gameMode == EndOfGame)
13428         EditGameEvent();
13429
13430     if (gameMode == EditPosition)
13431         EditPositionDone(TRUE);
13432
13433     if (WhiteOnMove(currentMove)) {
13434         DisplayError(_("It is not Black's turn"), 0);
13435         return;
13436     }
13437
13438     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13439       ExitAnalyzeMode();
13440
13441     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13442         gameMode == AnalyzeFile)
13443         TruncateGame();
13444
13445     ResurrectChessProgram();    /* in case it isn't running */
13446     gameMode = MachinePlaysBlack;
13447     pausing = FALSE;
13448     ModeHighlight();
13449     SetGameInfo();
13450     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13451     DisplayTitle(buf);
13452     if (first.sendName) {
13453       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13454       SendToProgram(buf, &first);
13455     }
13456     if (first.sendTime) {
13457       if (first.useColors) {
13458         SendToProgram("white\n", &first); /*gnu kludge*/
13459       }
13460       SendTimeRemaining(&first, FALSE);
13461     }
13462     if (first.useColors) {
13463       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13464     }
13465     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13466     SetMachineThinkingEnables();
13467     first.maybeThinking = TRUE;
13468     StartClocks();
13469
13470     if (appData.autoFlipView && flipView) {
13471       flipView = !flipView;
13472       DrawPosition(FALSE, NULL);
13473       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13474     }
13475     if(bookHit) { // [HGM] book: simulate book reply
13476         static char bookMove[MSG_SIZ]; // a bit generous?
13477
13478         programStats.nodes = programStats.depth = programStats.time =
13479         programStats.score = programStats.got_only_move = 0;
13480         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13481
13482         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13483         strcat(bookMove, bookHit);
13484         HandleMachineMove(bookMove, &first);
13485     }
13486 }
13487
13488
13489 void
13490 DisplayTwoMachinesTitle ()
13491 {
13492     char buf[MSG_SIZ];
13493     if (appData.matchGames > 0) {
13494         if(appData.tourneyFile[0]) {
13495           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13496                    gameInfo.white, _("vs."), gameInfo.black,
13497                    nextGame+1, appData.matchGames+1,
13498                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13499         } else 
13500         if (first.twoMachinesColor[0] == 'w') {
13501           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13502                    gameInfo.white, _("vs."),  gameInfo.black,
13503                    first.matchWins, second.matchWins,
13504                    matchGame - 1 - (first.matchWins + second.matchWins));
13505         } else {
13506           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13507                    gameInfo.white, _("vs."), gameInfo.black,
13508                    second.matchWins, first.matchWins,
13509                    matchGame - 1 - (first.matchWins + second.matchWins));
13510         }
13511     } else {
13512       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13513     }
13514     DisplayTitle(buf);
13515 }
13516
13517 void
13518 SettingsMenuIfReady ()
13519 {
13520   if (second.lastPing != second.lastPong) {
13521     DisplayMessage("", _("Waiting for second chess program"));
13522     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13523     return;
13524   }
13525   ThawUI();
13526   DisplayMessage("", "");
13527   SettingsPopUp(&second);
13528 }
13529
13530 int
13531 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13532 {
13533     char buf[MSG_SIZ];
13534     if (cps->pr == NoProc) {
13535         StartChessProgram(cps);
13536         if (cps->protocolVersion == 1) {
13537           retry();
13538         } else {
13539           /* kludge: allow timeout for initial "feature" command */
13540           FreezeUI();
13541           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13542           DisplayMessage("", buf);
13543           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13544         }
13545         return 1;
13546     }
13547     return 0;
13548 }
13549
13550 void
13551 TwoMachinesEvent P((void))
13552 {
13553     int i;
13554     char buf[MSG_SIZ];
13555     ChessProgramState *onmove;
13556     char *bookHit = NULL;
13557     static int stalling = 0;
13558     TimeMark now;
13559     long wait;
13560
13561     if (appData.noChessProgram) return;
13562
13563     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13564         DisplayError("second engine does not play this", 0);
13565         return;
13566     }
13567
13568     switch (gameMode) {
13569       case TwoMachinesPlay:
13570         return;
13571       case MachinePlaysWhite:
13572       case MachinePlaysBlack:
13573         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13574             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13575             return;
13576         }
13577         /* fall through */
13578       case BeginningOfGame:
13579       case PlayFromGameFile:
13580       case EndOfGame:
13581         EditGameEvent();
13582         if (gameMode != EditGame) return;
13583         break;
13584       case EditPosition:
13585         EditPositionDone(TRUE);
13586         break;
13587       case AnalyzeMode:
13588       case AnalyzeFile:
13589         ExitAnalyzeMode();
13590         break;
13591       case EditGame:
13592       default:
13593         break;
13594     }
13595
13596 //    forwardMostMove = currentMove;
13597     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13598
13599     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13600
13601     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13602     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13603       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13604       return;
13605     }
13606     if(!stalling) {
13607       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13608       SendToProgram("force\n", &second);
13609       stalling = 1;
13610       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13611       return;
13612     }
13613     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13614     if(appData.matchPause>10000 || appData.matchPause<10)
13615                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13616     wait = SubtractTimeMarks(&now, &pauseStart);
13617     if(wait < appData.matchPause) {
13618         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13619         return;
13620     }
13621     // we are now committed to starting the game
13622     stalling = 0;
13623     DisplayMessage("", "");
13624     if (startedFromSetupPosition) {
13625         SendBoard(&second, backwardMostMove);
13626     if (appData.debugMode) {
13627         fprintf(debugFP, "Two Machines\n");
13628     }
13629     }
13630     for (i = backwardMostMove; i < forwardMostMove; i++) {
13631         SendMoveToProgram(i, &second);
13632     }
13633
13634     gameMode = TwoMachinesPlay;
13635     pausing = FALSE;
13636     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13637     SetGameInfo();
13638     DisplayTwoMachinesTitle();
13639     firstMove = TRUE;
13640     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13641         onmove = &first;
13642     } else {
13643         onmove = &second;
13644     }
13645     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13646     SendToProgram(first.computerString, &first);
13647     if (first.sendName) {
13648       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13649       SendToProgram(buf, &first);
13650     }
13651     SendToProgram(second.computerString, &second);
13652     if (second.sendName) {
13653       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13654       SendToProgram(buf, &second);
13655     }
13656
13657     ResetClocks();
13658     if (!first.sendTime || !second.sendTime) {
13659         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13660         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13661     }
13662     if (onmove->sendTime) {
13663       if (onmove->useColors) {
13664         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13665       }
13666       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13667     }
13668     if (onmove->useColors) {
13669       SendToProgram(onmove->twoMachinesColor, onmove);
13670     }
13671     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13672 //    SendToProgram("go\n", onmove);
13673     onmove->maybeThinking = TRUE;
13674     SetMachineThinkingEnables();
13675
13676     StartClocks();
13677
13678     if(bookHit) { // [HGM] book: simulate book reply
13679         static char bookMove[MSG_SIZ]; // a bit generous?
13680
13681         programStats.nodes = programStats.depth = programStats.time =
13682         programStats.score = programStats.got_only_move = 0;
13683         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13684
13685         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13686         strcat(bookMove, bookHit);
13687         savedMessage = bookMove; // args for deferred call
13688         savedState = onmove;
13689         ScheduleDelayedEvent(DeferredBookMove, 1);
13690     }
13691 }
13692
13693 void
13694 TrainingEvent ()
13695 {
13696     if (gameMode == Training) {
13697       SetTrainingModeOff();
13698       gameMode = PlayFromGameFile;
13699       DisplayMessage("", _("Training mode off"));
13700     } else {
13701       gameMode = Training;
13702       animateTraining = appData.animate;
13703
13704       /* make sure we are not already at the end of the game */
13705       if (currentMove < forwardMostMove) {
13706         SetTrainingModeOn();
13707         DisplayMessage("", _("Training mode on"));
13708       } else {
13709         gameMode = PlayFromGameFile;
13710         DisplayError(_("Already at end of game"), 0);
13711       }
13712     }
13713     ModeHighlight();
13714 }
13715
13716 void
13717 IcsClientEvent ()
13718 {
13719     if (!appData.icsActive) return;
13720     switch (gameMode) {
13721       case IcsPlayingWhite:
13722       case IcsPlayingBlack:
13723       case IcsObserving:
13724       case IcsIdle:
13725       case BeginningOfGame:
13726       case IcsExamining:
13727         return;
13728
13729       case EditGame:
13730         break;
13731
13732       case EditPosition:
13733         EditPositionDone(TRUE);
13734         break;
13735
13736       case AnalyzeMode:
13737       case AnalyzeFile:
13738         ExitAnalyzeMode();
13739         break;
13740
13741       default:
13742         EditGameEvent();
13743         break;
13744     }
13745
13746     gameMode = IcsIdle;
13747     ModeHighlight();
13748     return;
13749 }
13750
13751 void
13752 EditGameEvent ()
13753 {
13754     int i;
13755
13756     switch (gameMode) {
13757       case Training:
13758         SetTrainingModeOff();
13759         break;
13760       case MachinePlaysWhite:
13761       case MachinePlaysBlack:
13762       case BeginningOfGame:
13763         SendToProgram("force\n", &first);
13764         SetUserThinkingEnables();
13765         break;
13766       case PlayFromGameFile:
13767         (void) StopLoadGameTimer();
13768         if (gameFileFP != NULL) {
13769             gameFileFP = NULL;
13770         }
13771         break;
13772       case EditPosition:
13773         EditPositionDone(TRUE);
13774         break;
13775       case AnalyzeMode:
13776       case AnalyzeFile:
13777         ExitAnalyzeMode();
13778         SendToProgram("force\n", &first);
13779         break;
13780       case TwoMachinesPlay:
13781         GameEnds(EndOfFile, NULL, GE_PLAYER);
13782         ResurrectChessProgram();
13783         SetUserThinkingEnables();
13784         break;
13785       case EndOfGame:
13786         ResurrectChessProgram();
13787         break;
13788       case IcsPlayingBlack:
13789       case IcsPlayingWhite:
13790         DisplayError(_("Warning: You are still playing a game"), 0);
13791         break;
13792       case IcsObserving:
13793         DisplayError(_("Warning: You are still observing a game"), 0);
13794         break;
13795       case IcsExamining:
13796         DisplayError(_("Warning: You are still examining a game"), 0);
13797         break;
13798       case IcsIdle:
13799         break;
13800       case EditGame:
13801       default:
13802         return;
13803     }
13804
13805     pausing = FALSE;
13806     StopClocks();
13807     first.offeredDraw = second.offeredDraw = 0;
13808
13809     if (gameMode == PlayFromGameFile) {
13810         whiteTimeRemaining = timeRemaining[0][currentMove];
13811         blackTimeRemaining = timeRemaining[1][currentMove];
13812         DisplayTitle("");
13813     }
13814
13815     if (gameMode == MachinePlaysWhite ||
13816         gameMode == MachinePlaysBlack ||
13817         gameMode == TwoMachinesPlay ||
13818         gameMode == EndOfGame) {
13819         i = forwardMostMove;
13820         while (i > currentMove) {
13821             SendToProgram("undo\n", &first);
13822             i--;
13823         }
13824         if(!adjustedClock) {
13825         whiteTimeRemaining = timeRemaining[0][currentMove];
13826         blackTimeRemaining = timeRemaining[1][currentMove];
13827         DisplayBothClocks();
13828         }
13829         if (whiteFlag || blackFlag) {
13830             whiteFlag = blackFlag = 0;
13831         }
13832         DisplayTitle("");
13833     }
13834
13835     gameMode = EditGame;
13836     ModeHighlight();
13837     SetGameInfo();
13838 }
13839
13840
13841 void
13842 EditPositionEvent ()
13843 {
13844     if (gameMode == EditPosition) {
13845         EditGameEvent();
13846         return;
13847     }
13848
13849     EditGameEvent();
13850     if (gameMode != EditGame) return;
13851
13852     gameMode = EditPosition;
13853     ModeHighlight();
13854     SetGameInfo();
13855     if (currentMove > 0)
13856       CopyBoard(boards[0], boards[currentMove]);
13857
13858     blackPlaysFirst = !WhiteOnMove(currentMove);
13859     ResetClocks();
13860     currentMove = forwardMostMove = backwardMostMove = 0;
13861     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13862     DisplayMove(-1);
13863     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13864 }
13865
13866 void
13867 ExitAnalyzeMode ()
13868 {
13869     /* [DM] icsEngineAnalyze - possible call from other functions */
13870     if (appData.icsEngineAnalyze) {
13871         appData.icsEngineAnalyze = FALSE;
13872
13873         DisplayMessage("",_("Close ICS engine analyze..."));
13874     }
13875     if (first.analysisSupport && first.analyzing) {
13876       SendToProgram("exit\n", &first);
13877       first.analyzing = FALSE;
13878     }
13879     thinkOutput[0] = NULLCHAR;
13880 }
13881
13882 void
13883 EditPositionDone (Boolean fakeRights)
13884 {
13885     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13886
13887     startedFromSetupPosition = TRUE;
13888     InitChessProgram(&first, FALSE);
13889     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13890       boards[0][EP_STATUS] = EP_NONE;
13891       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13892     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13893         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13894         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13895       } else boards[0][CASTLING][2] = NoRights;
13896     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13897         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13898         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13899       } else boards[0][CASTLING][5] = NoRights;
13900     }
13901     SendToProgram("force\n", &first);
13902     if (blackPlaysFirst) {
13903         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13904         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13905         currentMove = forwardMostMove = backwardMostMove = 1;
13906         CopyBoard(boards[1], boards[0]);
13907     } else {
13908         currentMove = forwardMostMove = backwardMostMove = 0;
13909     }
13910     SendBoard(&first, forwardMostMove);
13911     if (appData.debugMode) {
13912         fprintf(debugFP, "EditPosDone\n");
13913     }
13914     DisplayTitle("");
13915     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13916     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13917     gameMode = EditGame;
13918     ModeHighlight();
13919     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13920     ClearHighlights(); /* [AS] */
13921 }
13922
13923 /* Pause for `ms' milliseconds */
13924 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13925 void
13926 TimeDelay (long ms)
13927 {
13928     TimeMark m1, m2;
13929
13930     GetTimeMark(&m1);
13931     do {
13932         GetTimeMark(&m2);
13933     } while (SubtractTimeMarks(&m2, &m1) < ms);
13934 }
13935
13936 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13937 void
13938 SendMultiLineToICS (char *buf)
13939 {
13940     char temp[MSG_SIZ+1], *p;
13941     int len;
13942
13943     len = strlen(buf);
13944     if (len > MSG_SIZ)
13945       len = MSG_SIZ;
13946
13947     strncpy(temp, buf, len);
13948     temp[len] = 0;
13949
13950     p = temp;
13951     while (*p) {
13952         if (*p == '\n' || *p == '\r')
13953           *p = ' ';
13954         ++p;
13955     }
13956
13957     strcat(temp, "\n");
13958     SendToICS(temp);
13959     SendToPlayer(temp, strlen(temp));
13960 }
13961
13962 void
13963 SetWhiteToPlayEvent ()
13964 {
13965     if (gameMode == EditPosition) {
13966         blackPlaysFirst = FALSE;
13967         DisplayBothClocks();    /* works because currentMove is 0 */
13968     } else if (gameMode == IcsExamining) {
13969         SendToICS(ics_prefix);
13970         SendToICS("tomove white\n");
13971     }
13972 }
13973
13974 void
13975 SetBlackToPlayEvent ()
13976 {
13977     if (gameMode == EditPosition) {
13978         blackPlaysFirst = TRUE;
13979         currentMove = 1;        /* kludge */
13980         DisplayBothClocks();
13981         currentMove = 0;
13982     } else if (gameMode == IcsExamining) {
13983         SendToICS(ics_prefix);
13984         SendToICS("tomove black\n");
13985     }
13986 }
13987
13988 void
13989 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13990 {
13991     char buf[MSG_SIZ];
13992     ChessSquare piece = boards[0][y][x];
13993
13994     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13995
13996     switch (selection) {
13997       case ClearBoard:
13998         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13999             SendToICS(ics_prefix);
14000             SendToICS("bsetup clear\n");
14001         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14002             SendToICS(ics_prefix);
14003             SendToICS("clearboard\n");
14004         } else {
14005             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14006                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14007                 for (y = 0; y < BOARD_HEIGHT; y++) {
14008                     if (gameMode == IcsExamining) {
14009                         if (boards[currentMove][y][x] != EmptySquare) {
14010                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14011                                     AAA + x, ONE + y);
14012                             SendToICS(buf);
14013                         }
14014                     } else {
14015                         boards[0][y][x] = p;
14016                     }
14017                 }
14018             }
14019         }
14020         if (gameMode == EditPosition) {
14021             DrawPosition(FALSE, boards[0]);
14022         }
14023         break;
14024
14025       case WhitePlay:
14026         SetWhiteToPlayEvent();
14027         break;
14028
14029       case BlackPlay:
14030         SetBlackToPlayEvent();
14031         break;
14032
14033       case EmptySquare:
14034         if (gameMode == IcsExamining) {
14035             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14036             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14037             SendToICS(buf);
14038         } else {
14039             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14040                 if(x == BOARD_LEFT-2) {
14041                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14042                     boards[0][y][1] = 0;
14043                 } else
14044                 if(x == BOARD_RGHT+1) {
14045                     if(y >= gameInfo.holdingsSize) break;
14046                     boards[0][y][BOARD_WIDTH-2] = 0;
14047                 } else break;
14048             }
14049             boards[0][y][x] = EmptySquare;
14050             DrawPosition(FALSE, boards[0]);
14051         }
14052         break;
14053
14054       case PromotePiece:
14055         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14056            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14057             selection = (ChessSquare) (PROMOTED piece);
14058         } else if(piece == EmptySquare) selection = WhiteSilver;
14059         else selection = (ChessSquare)((int)piece - 1);
14060         goto defaultlabel;
14061
14062       case DemotePiece:
14063         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14064            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14065             selection = (ChessSquare) (DEMOTED piece);
14066         } else if(piece == EmptySquare) selection = BlackSilver;
14067         else selection = (ChessSquare)((int)piece + 1);
14068         goto defaultlabel;
14069
14070       case WhiteQueen:
14071       case BlackQueen:
14072         if(gameInfo.variant == VariantShatranj ||
14073            gameInfo.variant == VariantXiangqi  ||
14074            gameInfo.variant == VariantCourier  ||
14075            gameInfo.variant == VariantMakruk     )
14076             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14077         goto defaultlabel;
14078
14079       case WhiteKing:
14080       case BlackKing:
14081         if(gameInfo.variant == VariantXiangqi)
14082             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14083         if(gameInfo.variant == VariantKnightmate)
14084             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14085       default:
14086         defaultlabel:
14087         if (gameMode == IcsExamining) {
14088             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14089             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14090                      PieceToChar(selection), AAA + x, ONE + y);
14091             SendToICS(buf);
14092         } else {
14093             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14094                 int n;
14095                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14096                     n = PieceToNumber(selection - BlackPawn);
14097                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14098                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14099                     boards[0][BOARD_HEIGHT-1-n][1]++;
14100                 } else
14101                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14102                     n = PieceToNumber(selection);
14103                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14104                     boards[0][n][BOARD_WIDTH-1] = selection;
14105                     boards[0][n][BOARD_WIDTH-2]++;
14106                 }
14107             } else
14108             boards[0][y][x] = selection;
14109             DrawPosition(TRUE, boards[0]);
14110             ClearHighlights();
14111             fromX = fromY = -1;
14112         }
14113         break;
14114     }
14115 }
14116
14117
14118 void
14119 DropMenuEvent (ChessSquare selection, int x, int y)
14120 {
14121     ChessMove moveType;
14122
14123     switch (gameMode) {
14124       case IcsPlayingWhite:
14125       case MachinePlaysBlack:
14126         if (!WhiteOnMove(currentMove)) {
14127             DisplayMoveError(_("It is Black's turn"));
14128             return;
14129         }
14130         moveType = WhiteDrop;
14131         break;
14132       case IcsPlayingBlack:
14133       case MachinePlaysWhite:
14134         if (WhiteOnMove(currentMove)) {
14135             DisplayMoveError(_("It is White's turn"));
14136             return;
14137         }
14138         moveType = BlackDrop;
14139         break;
14140       case EditGame:
14141         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14142         break;
14143       default:
14144         return;
14145     }
14146
14147     if (moveType == BlackDrop && selection < BlackPawn) {
14148       selection = (ChessSquare) ((int) selection
14149                                  + (int) BlackPawn - (int) WhitePawn);
14150     }
14151     if (boards[currentMove][y][x] != EmptySquare) {
14152         DisplayMoveError(_("That square is occupied"));
14153         return;
14154     }
14155
14156     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14157 }
14158
14159 void
14160 AcceptEvent ()
14161 {
14162     /* Accept a pending offer of any kind from opponent */
14163
14164     if (appData.icsActive) {
14165         SendToICS(ics_prefix);
14166         SendToICS("accept\n");
14167     } else if (cmailMsgLoaded) {
14168         if (currentMove == cmailOldMove &&
14169             commentList[cmailOldMove] != NULL &&
14170             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14171                    "Black offers a draw" : "White offers a draw")) {
14172             TruncateGame();
14173             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14174             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14175         } else {
14176             DisplayError(_("There is no pending offer on this move"), 0);
14177             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14178         }
14179     } else {
14180         /* Not used for offers from chess program */
14181     }
14182 }
14183
14184 void
14185 DeclineEvent ()
14186 {
14187     /* Decline a pending offer of any kind from opponent */
14188
14189     if (appData.icsActive) {
14190         SendToICS(ics_prefix);
14191         SendToICS("decline\n");
14192     } else if (cmailMsgLoaded) {
14193         if (currentMove == cmailOldMove &&
14194             commentList[cmailOldMove] != NULL &&
14195             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14196                    "Black offers a draw" : "White offers a draw")) {
14197 #ifdef NOTDEF
14198             AppendComment(cmailOldMove, "Draw declined", TRUE);
14199             DisplayComment(cmailOldMove - 1, "Draw declined");
14200 #endif /*NOTDEF*/
14201         } else {
14202             DisplayError(_("There is no pending offer on this move"), 0);
14203         }
14204     } else {
14205         /* Not used for offers from chess program */
14206     }
14207 }
14208
14209 void
14210 RematchEvent ()
14211 {
14212     /* Issue ICS rematch command */
14213     if (appData.icsActive) {
14214         SendToICS(ics_prefix);
14215         SendToICS("rematch\n");
14216     }
14217 }
14218
14219 void
14220 CallFlagEvent ()
14221 {
14222     /* Call your opponent's flag (claim a win on time) */
14223     if (appData.icsActive) {
14224         SendToICS(ics_prefix);
14225         SendToICS("flag\n");
14226     } else {
14227         switch (gameMode) {
14228           default:
14229             return;
14230           case MachinePlaysWhite:
14231             if (whiteFlag) {
14232                 if (blackFlag)
14233                   GameEnds(GameIsDrawn, "Both players ran out of time",
14234                            GE_PLAYER);
14235                 else
14236                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14237             } else {
14238                 DisplayError(_("Your opponent is not out of time"), 0);
14239             }
14240             break;
14241           case MachinePlaysBlack:
14242             if (blackFlag) {
14243                 if (whiteFlag)
14244                   GameEnds(GameIsDrawn, "Both players ran out of time",
14245                            GE_PLAYER);
14246                 else
14247                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14248             } else {
14249                 DisplayError(_("Your opponent is not out of time"), 0);
14250             }
14251             break;
14252         }
14253     }
14254 }
14255
14256 void
14257 ClockClick (int which)
14258 {       // [HGM] code moved to back-end from winboard.c
14259         if(which) { // black clock
14260           if (gameMode == EditPosition || gameMode == IcsExamining) {
14261             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14262             SetBlackToPlayEvent();
14263           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14264           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14265           } else if (shiftKey) {
14266             AdjustClock(which, -1);
14267           } else if (gameMode == IcsPlayingWhite ||
14268                      gameMode == MachinePlaysBlack) {
14269             CallFlagEvent();
14270           }
14271         } else { // white clock
14272           if (gameMode == EditPosition || gameMode == IcsExamining) {
14273             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14274             SetWhiteToPlayEvent();
14275           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14276           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14277           } else if (shiftKey) {
14278             AdjustClock(which, -1);
14279           } else if (gameMode == IcsPlayingBlack ||
14280                    gameMode == MachinePlaysWhite) {
14281             CallFlagEvent();
14282           }
14283         }
14284 }
14285
14286 void
14287 DrawEvent ()
14288 {
14289     /* Offer draw or accept pending draw offer from opponent */
14290
14291     if (appData.icsActive) {
14292         /* Note: tournament rules require draw offers to be
14293            made after you make your move but before you punch
14294            your clock.  Currently ICS doesn't let you do that;
14295            instead, you immediately punch your clock after making
14296            a move, but you can offer a draw at any time. */
14297
14298         SendToICS(ics_prefix);
14299         SendToICS("draw\n");
14300         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14301     } else if (cmailMsgLoaded) {
14302         if (currentMove == cmailOldMove &&
14303             commentList[cmailOldMove] != NULL &&
14304             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14305                    "Black offers a draw" : "White offers a draw")) {
14306             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14307             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14308         } else if (currentMove == cmailOldMove + 1) {
14309             char *offer = WhiteOnMove(cmailOldMove) ?
14310               "White offers a draw" : "Black offers a draw";
14311             AppendComment(currentMove, offer, TRUE);
14312             DisplayComment(currentMove - 1, offer);
14313             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14314         } else {
14315             DisplayError(_("You must make your move before offering a draw"), 0);
14316             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14317         }
14318     } else if (first.offeredDraw) {
14319         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14320     } else {
14321         if (first.sendDrawOffers) {
14322             SendToProgram("draw\n", &first);
14323             userOfferedDraw = TRUE;
14324         }
14325     }
14326 }
14327
14328 void
14329 AdjournEvent ()
14330 {
14331     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14332
14333     if (appData.icsActive) {
14334         SendToICS(ics_prefix);
14335         SendToICS("adjourn\n");
14336     } else {
14337         /* Currently GNU Chess doesn't offer or accept Adjourns */
14338     }
14339 }
14340
14341
14342 void
14343 AbortEvent ()
14344 {
14345     /* Offer Abort or accept pending Abort offer from opponent */
14346
14347     if (appData.icsActive) {
14348         SendToICS(ics_prefix);
14349         SendToICS("abort\n");
14350     } else {
14351         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14352     }
14353 }
14354
14355 void
14356 ResignEvent ()
14357 {
14358     /* Resign.  You can do this even if it's not your turn. */
14359
14360     if (appData.icsActive) {
14361         SendToICS(ics_prefix);
14362         SendToICS("resign\n");
14363     } else {
14364         switch (gameMode) {
14365           case MachinePlaysWhite:
14366             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14367             break;
14368           case MachinePlaysBlack:
14369             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14370             break;
14371           case EditGame:
14372             if (cmailMsgLoaded) {
14373                 TruncateGame();
14374                 if (WhiteOnMove(cmailOldMove)) {
14375                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14376                 } else {
14377                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14378                 }
14379                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14380             }
14381             break;
14382           default:
14383             break;
14384         }
14385     }
14386 }
14387
14388
14389 void
14390 StopObservingEvent ()
14391 {
14392     /* Stop observing current games */
14393     SendToICS(ics_prefix);
14394     SendToICS("unobserve\n");
14395 }
14396
14397 void
14398 StopExaminingEvent ()
14399 {
14400     /* Stop observing current game */
14401     SendToICS(ics_prefix);
14402     SendToICS("unexamine\n");
14403 }
14404
14405 void
14406 ForwardInner (int target)
14407 {
14408     int limit; int oldSeekGraphUp = seekGraphUp;
14409
14410     if (appData.debugMode)
14411         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14412                 target, currentMove, forwardMostMove);
14413
14414     if (gameMode == EditPosition)
14415       return;
14416
14417     seekGraphUp = FALSE;
14418     MarkTargetSquares(1);
14419
14420     if (gameMode == PlayFromGameFile && !pausing)
14421       PauseEvent();
14422
14423     if (gameMode == IcsExamining && pausing)
14424       limit = pauseExamForwardMostMove;
14425     else
14426       limit = forwardMostMove;
14427
14428     if (target > limit) target = limit;
14429
14430     if (target > 0 && moveList[target - 1][0]) {
14431         int fromX, fromY, toX, toY;
14432         toX = moveList[target - 1][2] - AAA;
14433         toY = moveList[target - 1][3] - ONE;
14434         if (moveList[target - 1][1] == '@') {
14435             if (appData.highlightLastMove) {
14436                 SetHighlights(-1, -1, toX, toY);
14437             }
14438         } else {
14439             fromX = moveList[target - 1][0] - AAA;
14440             fromY = moveList[target - 1][1] - ONE;
14441             if (target == currentMove + 1) {
14442                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14443             }
14444             if (appData.highlightLastMove) {
14445                 SetHighlights(fromX, fromY, toX, toY);
14446             }
14447         }
14448     }
14449     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14450         gameMode == Training || gameMode == PlayFromGameFile ||
14451         gameMode == AnalyzeFile) {
14452         while (currentMove < target) {
14453             SendMoveToProgram(currentMove++, &first);
14454         }
14455     } else {
14456         currentMove = target;
14457     }
14458
14459     if (gameMode == EditGame || gameMode == EndOfGame) {
14460         whiteTimeRemaining = timeRemaining[0][currentMove];
14461         blackTimeRemaining = timeRemaining[1][currentMove];
14462     }
14463     DisplayBothClocks();
14464     DisplayMove(currentMove - 1);
14465     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14466     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14467     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14468         DisplayComment(currentMove - 1, commentList[currentMove]);
14469     }
14470     ClearMap(); // [HGM] exclude: invalidate map
14471 }
14472
14473
14474 void
14475 ForwardEvent ()
14476 {
14477     if (gameMode == IcsExamining && !pausing) {
14478         SendToICS(ics_prefix);
14479         SendToICS("forward\n");
14480     } else {
14481         ForwardInner(currentMove + 1);
14482     }
14483 }
14484
14485 void
14486 ToEndEvent ()
14487 {
14488     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14489         /* to optimze, we temporarily turn off analysis mode while we feed
14490          * the remaining moves to the engine. Otherwise we get analysis output
14491          * after each move.
14492          */
14493         if (first.analysisSupport) {
14494           SendToProgram("exit\nforce\n", &first);
14495           first.analyzing = FALSE;
14496         }
14497     }
14498
14499     if (gameMode == IcsExamining && !pausing) {
14500         SendToICS(ics_prefix);
14501         SendToICS("forward 999999\n");
14502     } else {
14503         ForwardInner(forwardMostMove);
14504     }
14505
14506     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14507         /* we have fed all the moves, so reactivate analysis mode */
14508         SendToProgram("analyze\n", &first);
14509         first.analyzing = TRUE;
14510         /*first.maybeThinking = TRUE;*/
14511         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14512     }
14513 }
14514
14515 void
14516 BackwardInner (int target)
14517 {
14518     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14519
14520     if (appData.debugMode)
14521         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14522                 target, currentMove, forwardMostMove);
14523
14524     if (gameMode == EditPosition) return;
14525     seekGraphUp = FALSE;
14526     MarkTargetSquares(1);
14527     if (currentMove <= backwardMostMove) {
14528         ClearHighlights();
14529         DrawPosition(full_redraw, boards[currentMove]);
14530         return;
14531     }
14532     if (gameMode == PlayFromGameFile && !pausing)
14533       PauseEvent();
14534
14535     if (moveList[target][0]) {
14536         int fromX, fromY, toX, toY;
14537         toX = moveList[target][2] - AAA;
14538         toY = moveList[target][3] - ONE;
14539         if (moveList[target][1] == '@') {
14540             if (appData.highlightLastMove) {
14541                 SetHighlights(-1, -1, toX, toY);
14542             }
14543         } else {
14544             fromX = moveList[target][0] - AAA;
14545             fromY = moveList[target][1] - ONE;
14546             if (target == currentMove - 1) {
14547                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14548             }
14549             if (appData.highlightLastMove) {
14550                 SetHighlights(fromX, fromY, toX, toY);
14551             }
14552         }
14553     }
14554     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14555         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14556         while (currentMove > target) {
14557             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14558                 // null move cannot be undone. Reload program with move history before it.
14559                 int i;
14560                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14561                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14562                 }
14563                 SendBoard(&first, i); 
14564                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14565                 break;
14566             }
14567             SendToProgram("undo\n", &first);
14568             currentMove--;
14569         }
14570     } else {
14571         currentMove = target;
14572     }
14573
14574     if (gameMode == EditGame || gameMode == EndOfGame) {
14575         whiteTimeRemaining = timeRemaining[0][currentMove];
14576         blackTimeRemaining = timeRemaining[1][currentMove];
14577     }
14578     DisplayBothClocks();
14579     DisplayMove(currentMove - 1);
14580     DrawPosition(full_redraw, boards[currentMove]);
14581     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14582     // [HGM] PV info: routine tests if comment empty
14583     DisplayComment(currentMove - 1, commentList[currentMove]);
14584     ClearMap(); // [HGM] exclude: invalidate map
14585 }
14586
14587 void
14588 BackwardEvent ()
14589 {
14590     if (gameMode == IcsExamining && !pausing) {
14591         SendToICS(ics_prefix);
14592         SendToICS("backward\n");
14593     } else {
14594         BackwardInner(currentMove - 1);
14595     }
14596 }
14597
14598 void
14599 ToStartEvent ()
14600 {
14601     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14602         /* to optimize, we temporarily turn off analysis mode while we undo
14603          * all the moves. Otherwise we get analysis output after each undo.
14604          */
14605         if (first.analysisSupport) {
14606           SendToProgram("exit\nforce\n", &first);
14607           first.analyzing = FALSE;
14608         }
14609     }
14610
14611     if (gameMode == IcsExamining && !pausing) {
14612         SendToICS(ics_prefix);
14613         SendToICS("backward 999999\n");
14614     } else {
14615         BackwardInner(backwardMostMove);
14616     }
14617
14618     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14619         /* we have fed all the moves, so reactivate analysis mode */
14620         SendToProgram("analyze\n", &first);
14621         first.analyzing = TRUE;
14622         /*first.maybeThinking = TRUE;*/
14623         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14624     }
14625 }
14626
14627 void
14628 ToNrEvent (int to)
14629 {
14630   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14631   if (to >= forwardMostMove) to = forwardMostMove;
14632   if (to <= backwardMostMove) to = backwardMostMove;
14633   if (to < currentMove) {
14634     BackwardInner(to);
14635   } else {
14636     ForwardInner(to);
14637   }
14638 }
14639
14640 void
14641 RevertEvent (Boolean annotate)
14642 {
14643     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14644         return;
14645     }
14646     if (gameMode != IcsExamining) {
14647         DisplayError(_("You are not examining a game"), 0);
14648         return;
14649     }
14650     if (pausing) {
14651         DisplayError(_("You can't revert while pausing"), 0);
14652         return;
14653     }
14654     SendToICS(ics_prefix);
14655     SendToICS("revert\n");
14656 }
14657
14658 void
14659 RetractMoveEvent ()
14660 {
14661     switch (gameMode) {
14662       case MachinePlaysWhite:
14663       case MachinePlaysBlack:
14664         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14665             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14666             return;
14667         }
14668         if (forwardMostMove < 2) return;
14669         currentMove = forwardMostMove = forwardMostMove - 2;
14670         whiteTimeRemaining = timeRemaining[0][currentMove];
14671         blackTimeRemaining = timeRemaining[1][currentMove];
14672         DisplayBothClocks();
14673         DisplayMove(currentMove - 1);
14674         ClearHighlights();/*!! could figure this out*/
14675         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14676         SendToProgram("remove\n", &first);
14677         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14678         break;
14679
14680       case BeginningOfGame:
14681       default:
14682         break;
14683
14684       case IcsPlayingWhite:
14685       case IcsPlayingBlack:
14686         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14687             SendToICS(ics_prefix);
14688             SendToICS("takeback 2\n");
14689         } else {
14690             SendToICS(ics_prefix);
14691             SendToICS("takeback 1\n");
14692         }
14693         break;
14694     }
14695 }
14696
14697 void
14698 MoveNowEvent ()
14699 {
14700     ChessProgramState *cps;
14701
14702     switch (gameMode) {
14703       case MachinePlaysWhite:
14704         if (!WhiteOnMove(forwardMostMove)) {
14705             DisplayError(_("It is your turn"), 0);
14706             return;
14707         }
14708         cps = &first;
14709         break;
14710       case MachinePlaysBlack:
14711         if (WhiteOnMove(forwardMostMove)) {
14712             DisplayError(_("It is your turn"), 0);
14713             return;
14714         }
14715         cps = &first;
14716         break;
14717       case TwoMachinesPlay:
14718         if (WhiteOnMove(forwardMostMove) ==
14719             (first.twoMachinesColor[0] == 'w')) {
14720             cps = &first;
14721         } else {
14722             cps = &second;
14723         }
14724         break;
14725       case BeginningOfGame:
14726       default:
14727         return;
14728     }
14729     SendToProgram("?\n", cps);
14730 }
14731
14732 void
14733 TruncateGameEvent ()
14734 {
14735     EditGameEvent();
14736     if (gameMode != EditGame) return;
14737     TruncateGame();
14738 }
14739
14740 void
14741 TruncateGame ()
14742 {
14743     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14744     if (forwardMostMove > currentMove) {
14745         if (gameInfo.resultDetails != NULL) {
14746             free(gameInfo.resultDetails);
14747             gameInfo.resultDetails = NULL;
14748             gameInfo.result = GameUnfinished;
14749         }
14750         forwardMostMove = currentMove;
14751         HistorySet(parseList, backwardMostMove, forwardMostMove,
14752                    currentMove-1);
14753     }
14754 }
14755
14756 void
14757 HintEvent ()
14758 {
14759     if (appData.noChessProgram) return;
14760     switch (gameMode) {
14761       case MachinePlaysWhite:
14762         if (WhiteOnMove(forwardMostMove)) {
14763             DisplayError(_("Wait until your turn"), 0);
14764             return;
14765         }
14766         break;
14767       case BeginningOfGame:
14768       case MachinePlaysBlack:
14769         if (!WhiteOnMove(forwardMostMove)) {
14770             DisplayError(_("Wait until your turn"), 0);
14771             return;
14772         }
14773         break;
14774       default:
14775         DisplayError(_("No hint available"), 0);
14776         return;
14777     }
14778     SendToProgram("hint\n", &first);
14779     hintRequested = TRUE;
14780 }
14781
14782 void
14783 BookEvent ()
14784 {
14785     if (appData.noChessProgram) return;
14786     switch (gameMode) {
14787       case MachinePlaysWhite:
14788         if (WhiteOnMove(forwardMostMove)) {
14789             DisplayError(_("Wait until your turn"), 0);
14790             return;
14791         }
14792         break;
14793       case BeginningOfGame:
14794       case MachinePlaysBlack:
14795         if (!WhiteOnMove(forwardMostMove)) {
14796             DisplayError(_("Wait until your turn"), 0);
14797             return;
14798         }
14799         break;
14800       case EditPosition:
14801         EditPositionDone(TRUE);
14802         break;
14803       case TwoMachinesPlay:
14804         return;
14805       default:
14806         break;
14807     }
14808     SendToProgram("bk\n", &first);
14809     bookOutput[0] = NULLCHAR;
14810     bookRequested = TRUE;
14811 }
14812
14813 void
14814 AboutGameEvent ()
14815 {
14816     char *tags = PGNTags(&gameInfo);
14817     TagsPopUp(tags, CmailMsg());
14818     free(tags);
14819 }
14820
14821 /* end button procedures */
14822
14823 void
14824 PrintPosition (FILE *fp, int move)
14825 {
14826     int i, j;
14827
14828     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14829         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14830             char c = PieceToChar(boards[move][i][j]);
14831             fputc(c == 'x' ? '.' : c, fp);
14832             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14833         }
14834     }
14835     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14836       fprintf(fp, "white to play\n");
14837     else
14838       fprintf(fp, "black to play\n");
14839 }
14840
14841 void
14842 PrintOpponents (FILE *fp)
14843 {
14844     if (gameInfo.white != NULL) {
14845         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14846     } else {
14847         fprintf(fp, "\n");
14848     }
14849 }
14850
14851 /* Find last component of program's own name, using some heuristics */
14852 void
14853 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14854 {
14855     char *p, *q, c;
14856     int local = (strcmp(host, "localhost") == 0);
14857     while (!local && (p = strchr(prog, ';')) != NULL) {
14858         p++;
14859         while (*p == ' ') p++;
14860         prog = p;
14861     }
14862     if (*prog == '"' || *prog == '\'') {
14863         q = strchr(prog + 1, *prog);
14864     } else {
14865         q = strchr(prog, ' ');
14866     }
14867     if (q == NULL) q = prog + strlen(prog);
14868     p = q;
14869     while (p >= prog && *p != '/' && *p != '\\') p--;
14870     p++;
14871     if(p == prog && *p == '"') p++;
14872     c = *q; *q = 0;
14873     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14874     memcpy(buf, p, q - p);
14875     buf[q - p] = NULLCHAR;
14876     if (!local) {
14877         strcat(buf, "@");
14878         strcat(buf, host);
14879     }
14880 }
14881
14882 char *
14883 TimeControlTagValue ()
14884 {
14885     char buf[MSG_SIZ];
14886     if (!appData.clockMode) {
14887       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14888     } else if (movesPerSession > 0) {
14889       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14890     } else if (timeIncrement == 0) {
14891       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14892     } else {
14893       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14894     }
14895     return StrSave(buf);
14896 }
14897
14898 void
14899 SetGameInfo ()
14900 {
14901     /* This routine is used only for certain modes */
14902     VariantClass v = gameInfo.variant;
14903     ChessMove r = GameUnfinished;
14904     char *p = NULL;
14905
14906     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14907         r = gameInfo.result;
14908         p = gameInfo.resultDetails;
14909         gameInfo.resultDetails = NULL;
14910     }
14911     ClearGameInfo(&gameInfo);
14912     gameInfo.variant = v;
14913
14914     switch (gameMode) {
14915       case MachinePlaysWhite:
14916         gameInfo.event = StrSave( appData.pgnEventHeader );
14917         gameInfo.site = StrSave(HostName());
14918         gameInfo.date = PGNDate();
14919         gameInfo.round = StrSave("-");
14920         gameInfo.white = StrSave(first.tidy);
14921         gameInfo.black = StrSave(UserName());
14922         gameInfo.timeControl = TimeControlTagValue();
14923         break;
14924
14925       case MachinePlaysBlack:
14926         gameInfo.event = StrSave( appData.pgnEventHeader );
14927         gameInfo.site = StrSave(HostName());
14928         gameInfo.date = PGNDate();
14929         gameInfo.round = StrSave("-");
14930         gameInfo.white = StrSave(UserName());
14931         gameInfo.black = StrSave(first.tidy);
14932         gameInfo.timeControl = TimeControlTagValue();
14933         break;
14934
14935       case TwoMachinesPlay:
14936         gameInfo.event = StrSave( appData.pgnEventHeader );
14937         gameInfo.site = StrSave(HostName());
14938         gameInfo.date = PGNDate();
14939         if (roundNr > 0) {
14940             char buf[MSG_SIZ];
14941             snprintf(buf, MSG_SIZ, "%d", roundNr);
14942             gameInfo.round = StrSave(buf);
14943         } else {
14944             gameInfo.round = StrSave("-");
14945         }
14946         if (first.twoMachinesColor[0] == 'w') {
14947             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14948             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14949         } else {
14950             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14951             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14952         }
14953         gameInfo.timeControl = TimeControlTagValue();
14954         break;
14955
14956       case EditGame:
14957         gameInfo.event = StrSave("Edited game");
14958         gameInfo.site = StrSave(HostName());
14959         gameInfo.date = PGNDate();
14960         gameInfo.round = StrSave("-");
14961         gameInfo.white = StrSave("-");
14962         gameInfo.black = StrSave("-");
14963         gameInfo.result = r;
14964         gameInfo.resultDetails = p;
14965         break;
14966
14967       case EditPosition:
14968         gameInfo.event = StrSave("Edited position");
14969         gameInfo.site = StrSave(HostName());
14970         gameInfo.date = PGNDate();
14971         gameInfo.round = StrSave("-");
14972         gameInfo.white = StrSave("-");
14973         gameInfo.black = StrSave("-");
14974         break;
14975
14976       case IcsPlayingWhite:
14977       case IcsPlayingBlack:
14978       case IcsObserving:
14979       case IcsExamining:
14980         break;
14981
14982       case PlayFromGameFile:
14983         gameInfo.event = StrSave("Game from non-PGN file");
14984         gameInfo.site = StrSave(HostName());
14985         gameInfo.date = PGNDate();
14986         gameInfo.round = StrSave("-");
14987         gameInfo.white = StrSave("?");
14988         gameInfo.black = StrSave("?");
14989         break;
14990
14991       default:
14992         break;
14993     }
14994 }
14995
14996 void
14997 ReplaceComment (int index, char *text)
14998 {
14999     int len;
15000     char *p;
15001     float score;
15002
15003     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15004        pvInfoList[index-1].depth == len &&
15005        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15006        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15007     while (*text == '\n') text++;
15008     len = strlen(text);
15009     while (len > 0 && text[len - 1] == '\n') len--;
15010
15011     if (commentList[index] != NULL)
15012       free(commentList[index]);
15013
15014     if (len == 0) {
15015         commentList[index] = NULL;
15016         return;
15017     }
15018   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15019       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15020       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15021     commentList[index] = (char *) malloc(len + 2);
15022     strncpy(commentList[index], text, len);
15023     commentList[index][len] = '\n';
15024     commentList[index][len + 1] = NULLCHAR;
15025   } else {
15026     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15027     char *p;
15028     commentList[index] = (char *) malloc(len + 7);
15029     safeStrCpy(commentList[index], "{\n", 3);
15030     safeStrCpy(commentList[index]+2, text, len+1);
15031     commentList[index][len+2] = NULLCHAR;
15032     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15033     strcat(commentList[index], "\n}\n");
15034   }
15035 }
15036
15037 void
15038 CrushCRs (char *text)
15039 {
15040   char *p = text;
15041   char *q = text;
15042   char ch;
15043
15044   do {
15045     ch = *p++;
15046     if (ch == '\r') continue;
15047     *q++ = ch;
15048   } while (ch != '\0');
15049 }
15050
15051 void
15052 AppendComment (int index, char *text, Boolean addBraces)
15053 /* addBraces  tells if we should add {} */
15054 {
15055     int oldlen, len;
15056     char *old;
15057
15058 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15059     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15060
15061     CrushCRs(text);
15062     while (*text == '\n') text++;
15063     len = strlen(text);
15064     while (len > 0 && text[len - 1] == '\n') len--;
15065     text[len] = NULLCHAR;
15066
15067     if (len == 0) return;
15068
15069     if (commentList[index] != NULL) {
15070       Boolean addClosingBrace = addBraces;
15071         old = commentList[index];
15072         oldlen = strlen(old);
15073         while(commentList[index][oldlen-1] ==  '\n')
15074           commentList[index][--oldlen] = NULLCHAR;
15075         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15076         safeStrCpy(commentList[index], old, oldlen + len + 6);
15077         free(old);
15078         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15079         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15080           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15081           while (*text == '\n') { text++; len--; }
15082           commentList[index][--oldlen] = NULLCHAR;
15083       }
15084         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15085         else          strcat(commentList[index], "\n");
15086         strcat(commentList[index], text);
15087         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15088         else          strcat(commentList[index], "\n");
15089     } else {
15090         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15091         if(addBraces)
15092           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15093         else commentList[index][0] = NULLCHAR;
15094         strcat(commentList[index], text);
15095         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15096         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15097     }
15098 }
15099
15100 static char *
15101 FindStr (char * text, char * sub_text)
15102 {
15103     char * result = strstr( text, sub_text );
15104
15105     if( result != NULL ) {
15106         result += strlen( sub_text );
15107     }
15108
15109     return result;
15110 }
15111
15112 /* [AS] Try to extract PV info from PGN comment */
15113 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15114 char *
15115 GetInfoFromComment (int index, char * text)
15116 {
15117     char * sep = text, *p;
15118
15119     if( text != NULL && index > 0 ) {
15120         int score = 0;
15121         int depth = 0;
15122         int time = -1, sec = 0, deci;
15123         char * s_eval = FindStr( text, "[%eval " );
15124         char * s_emt = FindStr( text, "[%emt " );
15125
15126         if( s_eval != NULL || s_emt != NULL ) {
15127             /* New style */
15128             char delim;
15129
15130             if( s_eval != NULL ) {
15131                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15132                     return text;
15133                 }
15134
15135                 if( delim != ']' ) {
15136                     return text;
15137                 }
15138             }
15139
15140             if( s_emt != NULL ) {
15141             }
15142                 return text;
15143         }
15144         else {
15145             /* We expect something like: [+|-]nnn.nn/dd */
15146             int score_lo = 0;
15147
15148             if(*text != '{') return text; // [HGM] braces: must be normal comment
15149
15150             sep = strchr( text, '/' );
15151             if( sep == NULL || sep < (text+4) ) {
15152                 return text;
15153             }
15154
15155             p = text;
15156             if(p[1] == '(') { // comment starts with PV
15157                p = strchr(p, ')'); // locate end of PV
15158                if(p == NULL || sep < p+5) return text;
15159                // at this point we have something like "{(.*) +0.23/6 ..."
15160                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15161                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15162                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15163             }
15164             time = -1; sec = -1; deci = -1;
15165             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15166                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15167                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15168                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15169                 return text;
15170             }
15171
15172             if( score_lo < 0 || score_lo >= 100 ) {
15173                 return text;
15174             }
15175
15176             if(sec >= 0) time = 600*time + 10*sec; else
15177             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15178
15179             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15180
15181             /* [HGM] PV time: now locate end of PV info */
15182             while( *++sep >= '0' && *sep <= '9'); // strip depth
15183             if(time >= 0)
15184             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15185             if(sec >= 0)
15186             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15187             if(deci >= 0)
15188             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15189             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15190         }
15191
15192         if( depth <= 0 ) {
15193             return text;
15194         }
15195
15196         if( time < 0 ) {
15197             time = -1;
15198         }
15199
15200         pvInfoList[index-1].depth = depth;
15201         pvInfoList[index-1].score = score;
15202         pvInfoList[index-1].time  = 10*time; // centi-sec
15203         if(*sep == '}') *sep = 0; else *--sep = '{';
15204         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15205     }
15206     return sep;
15207 }
15208
15209 void
15210 SendToProgram (char *message, ChessProgramState *cps)
15211 {
15212     int count, outCount, error;
15213     char buf[MSG_SIZ];
15214
15215     if (cps->pr == NoProc) return;
15216     Attention(cps);
15217
15218     if (appData.debugMode) {
15219         TimeMark now;
15220         GetTimeMark(&now);
15221         fprintf(debugFP, "%ld >%-6s: %s",
15222                 SubtractTimeMarks(&now, &programStartTime),
15223                 cps->which, message);
15224         if(serverFP)
15225             fprintf(serverFP, "%ld >%-6s: %s",
15226                 SubtractTimeMarks(&now, &programStartTime),
15227                 cps->which, message), fflush(serverFP);
15228     }
15229
15230     count = strlen(message);
15231     outCount = OutputToProcess(cps->pr, message, count, &error);
15232     if (outCount < count && !exiting
15233                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15234       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15235       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15236         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15237             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15238                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15239                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15240                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15241             } else {
15242                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15243                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15244                 gameInfo.result = res;
15245             }
15246             gameInfo.resultDetails = StrSave(buf);
15247         }
15248         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15249         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15250     }
15251 }
15252
15253 void
15254 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15255 {
15256     char *end_str;
15257     char buf[MSG_SIZ];
15258     ChessProgramState *cps = (ChessProgramState *)closure;
15259
15260     if (isr != cps->isr) return; /* Killed intentionally */
15261     if (count <= 0) {
15262         if (count == 0) {
15263             RemoveInputSource(cps->isr);
15264             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15265             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15266                     _(cps->which), cps->program);
15267         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15268                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15269                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15270                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15271                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15272                 } else {
15273                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15274                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15275                     gameInfo.result = res;
15276                 }
15277                 gameInfo.resultDetails = StrSave(buf);
15278             }
15279             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15280             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15281         } else {
15282             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15283                     _(cps->which), cps->program);
15284             RemoveInputSource(cps->isr);
15285
15286             /* [AS] Program is misbehaving badly... kill it */
15287             if( count == -2 ) {
15288                 DestroyChildProcess( cps->pr, 9 );
15289                 cps->pr = NoProc;
15290             }
15291
15292             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15293         }
15294         return;
15295     }
15296
15297     if ((end_str = strchr(message, '\r')) != NULL)
15298       *end_str = NULLCHAR;
15299     if ((end_str = strchr(message, '\n')) != NULL)
15300       *end_str = NULLCHAR;
15301
15302     if (appData.debugMode) {
15303         TimeMark now; int print = 1;
15304         char *quote = ""; char c; int i;
15305
15306         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15307                 char start = message[0];
15308                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15309                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15310                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15311                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15312                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15313                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15314                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15315                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15316                    sscanf(message, "hint: %c", &c)!=1 && 
15317                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15318                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15319                     print = (appData.engineComments >= 2);
15320                 }
15321                 message[0] = start; // restore original message
15322         }
15323         if(print) {
15324                 GetTimeMark(&now);
15325                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15326                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15327                         quote,
15328                         message);
15329                 if(serverFP)
15330                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15331                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15332                         quote,
15333                         message), fflush(serverFP);
15334         }
15335     }
15336
15337     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15338     if (appData.icsEngineAnalyze) {
15339         if (strstr(message, "whisper") != NULL ||
15340              strstr(message, "kibitz") != NULL ||
15341             strstr(message, "tellics") != NULL) return;
15342     }
15343
15344     HandleMachineMove(message, cps);
15345 }
15346
15347
15348 void
15349 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15350 {
15351     char buf[MSG_SIZ];
15352     int seconds;
15353
15354     if( timeControl_2 > 0 ) {
15355         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15356             tc = timeControl_2;
15357         }
15358     }
15359     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15360     inc /= cps->timeOdds;
15361     st  /= cps->timeOdds;
15362
15363     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15364
15365     if (st > 0) {
15366       /* Set exact time per move, normally using st command */
15367       if (cps->stKludge) {
15368         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15369         seconds = st % 60;
15370         if (seconds == 0) {
15371           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15372         } else {
15373           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15374         }
15375       } else {
15376         snprintf(buf, MSG_SIZ, "st %d\n", st);
15377       }
15378     } else {
15379       /* Set conventional or incremental time control, using level command */
15380       if (seconds == 0) {
15381         /* Note old gnuchess bug -- minutes:seconds used to not work.
15382            Fixed in later versions, but still avoid :seconds
15383            when seconds is 0. */
15384         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15385       } else {
15386         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15387                  seconds, inc/1000.);
15388       }
15389     }
15390     SendToProgram(buf, cps);
15391
15392     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15393     /* Orthogonally, limit search to given depth */
15394     if (sd > 0) {
15395       if (cps->sdKludge) {
15396         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15397       } else {
15398         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15399       }
15400       SendToProgram(buf, cps);
15401     }
15402
15403     if(cps->nps >= 0) { /* [HGM] nps */
15404         if(cps->supportsNPS == FALSE)
15405           cps->nps = -1; // don't use if engine explicitly says not supported!
15406         else {
15407           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15408           SendToProgram(buf, cps);
15409         }
15410     }
15411 }
15412
15413 ChessProgramState *
15414 WhitePlayer ()
15415 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15416 {
15417     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15418        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15419         return &second;
15420     return &first;
15421 }
15422
15423 void
15424 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15425 {
15426     char message[MSG_SIZ];
15427     long time, otime;
15428
15429     /* Note: this routine must be called when the clocks are stopped
15430        or when they have *just* been set or switched; otherwise
15431        it will be off by the time since the current tick started.
15432     */
15433     if (machineWhite) {
15434         time = whiteTimeRemaining / 10;
15435         otime = blackTimeRemaining / 10;
15436     } else {
15437         time = blackTimeRemaining / 10;
15438         otime = whiteTimeRemaining / 10;
15439     }
15440     /* [HGM] translate opponent's time by time-odds factor */
15441     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15442
15443     if (time <= 0) time = 1;
15444     if (otime <= 0) otime = 1;
15445
15446     snprintf(message, MSG_SIZ, "time %ld\n", time);
15447     SendToProgram(message, cps);
15448
15449     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15450     SendToProgram(message, cps);
15451 }
15452
15453 int
15454 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15455 {
15456   char buf[MSG_SIZ];
15457   int len = strlen(name);
15458   int val;
15459
15460   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15461     (*p) += len + 1;
15462     sscanf(*p, "%d", &val);
15463     *loc = (val != 0);
15464     while (**p && **p != ' ')
15465       (*p)++;
15466     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15467     SendToProgram(buf, cps);
15468     return TRUE;
15469   }
15470   return FALSE;
15471 }
15472
15473 int
15474 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15475 {
15476   char buf[MSG_SIZ];
15477   int len = strlen(name);
15478   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15479     (*p) += len + 1;
15480     sscanf(*p, "%d", loc);
15481     while (**p && **p != ' ') (*p)++;
15482     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15483     SendToProgram(buf, cps);
15484     return TRUE;
15485   }
15486   return FALSE;
15487 }
15488
15489 int
15490 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15491 {
15492   char buf[MSG_SIZ];
15493   int len = strlen(name);
15494   if (strncmp((*p), name, len) == 0
15495       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15496     (*p) += len + 2;
15497     sscanf(*p, "%[^\"]", loc);
15498     while (**p && **p != '\"') (*p)++;
15499     if (**p == '\"') (*p)++;
15500     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15501     SendToProgram(buf, cps);
15502     return TRUE;
15503   }
15504   return FALSE;
15505 }
15506
15507 int
15508 ParseOption (Option *opt, ChessProgramState *cps)
15509 // [HGM] options: process the string that defines an engine option, and determine
15510 // name, type, default value, and allowed value range
15511 {
15512         char *p, *q, buf[MSG_SIZ];
15513         int n, min = (-1)<<31, max = 1<<31, def;
15514
15515         if(p = strstr(opt->name, " -spin ")) {
15516             if((n = sscanf(p, " -spin %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;
15524         } else if((p = strstr(opt->name, " -slider "))) {
15525             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15526             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15527             if(max < min) max = min; // enforce consistency
15528             if(def < min) def = min;
15529             if(def > max) def = max;
15530             opt->value = def;
15531             opt->min = min;
15532             opt->max = max;
15533             opt->type = Spin; // Slider;
15534         } else if((p = strstr(opt->name, " -string "))) {
15535             opt->textValue = p+9;
15536             opt->type = TextBox;
15537         } else if((p = strstr(opt->name, " -file "))) {
15538             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15539             opt->textValue = p+7;
15540             opt->type = FileName; // FileName;
15541         } else if((p = strstr(opt->name, " -path "))) {
15542             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15543             opt->textValue = p+7;
15544             opt->type = PathName; // PathName;
15545         } else if(p = strstr(opt->name, " -check ")) {
15546             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15547             opt->value = (def != 0);
15548             opt->type = CheckBox;
15549         } else if(p = strstr(opt->name, " -combo ")) {
15550             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15551             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15552             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15553             opt->value = n = 0;
15554             while(q = StrStr(q, " /// ")) {
15555                 n++; *q = 0;    // count choices, and null-terminate each of them
15556                 q += 5;
15557                 if(*q == '*') { // remember default, which is marked with * prefix
15558                     q++;
15559                     opt->value = n;
15560                 }
15561                 cps->comboList[cps->comboCnt++] = q;
15562             }
15563             cps->comboList[cps->comboCnt++] = NULL;
15564             opt->max = n + 1;
15565             opt->type = ComboBox;
15566         } else if(p = strstr(opt->name, " -button")) {
15567             opt->type = Button;
15568         } else if(p = strstr(opt->name, " -save")) {
15569             opt->type = SaveButton;
15570         } else return FALSE;
15571         *p = 0; // terminate option name
15572         // now look if the command-line options define a setting for this engine option.
15573         if(cps->optionSettings && cps->optionSettings[0])
15574             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15575         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15576           snprintf(buf, MSG_SIZ, "option %s", p);
15577                 if(p = strstr(buf, ",")) *p = 0;
15578                 if(q = strchr(buf, '=')) switch(opt->type) {
15579                     case ComboBox:
15580                         for(n=0; n<opt->max; n++)
15581                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15582                         break;
15583                     case TextBox:
15584                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15585                         break;
15586                     case Spin:
15587                     case CheckBox:
15588                         opt->value = atoi(q+1);
15589                     default:
15590                         break;
15591                 }
15592                 strcat(buf, "\n");
15593                 SendToProgram(buf, cps);
15594         }
15595         return TRUE;
15596 }
15597
15598 void
15599 FeatureDone (ChessProgramState *cps, int val)
15600 {
15601   DelayedEventCallback cb = GetDelayedEvent();
15602   if ((cb == InitBackEnd3 && cps == &first) ||
15603       (cb == SettingsMenuIfReady && cps == &second) ||
15604       (cb == LoadEngine) ||
15605       (cb == TwoMachinesEventIfReady)) {
15606     CancelDelayedEvent();
15607     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15608   }
15609   cps->initDone = val;
15610 }
15611
15612 /* Parse feature command from engine */
15613 void
15614 ParseFeatures (char *args, ChessProgramState *cps)
15615 {
15616   char *p = args;
15617   char *q;
15618   int val;
15619   char buf[MSG_SIZ];
15620
15621   for (;;) {
15622     while (*p == ' ') p++;
15623     if (*p == NULLCHAR) return;
15624
15625     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15626     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15627     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15628     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15629     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15630     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15631     if (BoolFeature(&p, "reuse", &val, cps)) {
15632       /* Engine can disable reuse, but can't enable it if user said no */
15633       if (!val) cps->reuse = FALSE;
15634       continue;
15635     }
15636     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15637     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15638       if (gameMode == TwoMachinesPlay) {
15639         DisplayTwoMachinesTitle();
15640       } else {
15641         DisplayTitle("");
15642       }
15643       continue;
15644     }
15645     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15646     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15647     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15648     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15649     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15650     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15651     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15652     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15653     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15654     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15655     if (IntFeature(&p, "done", &val, cps)) {
15656       FeatureDone(cps, val);
15657       continue;
15658     }
15659     /* Added by Tord: */
15660     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15661     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15662     /* End of additions by Tord */
15663
15664     /* [HGM] added features: */
15665     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15666     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15667     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15668     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15669     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15670     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15671     if (StringFeature(&p, "option", buf, cps)) {
15672         FREE(cps->option[cps->nrOptions].name);
15673         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15674         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15675         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15676           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15677             SendToProgram(buf, cps);
15678             continue;
15679         }
15680         if(cps->nrOptions >= MAX_OPTIONS) {
15681             cps->nrOptions--;
15682             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15683             DisplayError(buf, 0);
15684         }
15685         continue;
15686     }
15687     /* End of additions by HGM */
15688
15689     /* unknown feature: complain and skip */
15690     q = p;
15691     while (*q && *q != '=') q++;
15692     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15693     SendToProgram(buf, cps);
15694     p = q;
15695     if (*p == '=') {
15696       p++;
15697       if (*p == '\"') {
15698         p++;
15699         while (*p && *p != '\"') p++;
15700         if (*p == '\"') p++;
15701       } else {
15702         while (*p && *p != ' ') p++;
15703       }
15704     }
15705   }
15706
15707 }
15708
15709 void
15710 PeriodicUpdatesEvent (int newState)
15711 {
15712     if (newState == appData.periodicUpdates)
15713       return;
15714
15715     appData.periodicUpdates=newState;
15716
15717     /* Display type changes, so update it now */
15718 //    DisplayAnalysis();
15719
15720     /* Get the ball rolling again... */
15721     if (newState) {
15722         AnalysisPeriodicEvent(1);
15723         StartAnalysisClock();
15724     }
15725 }
15726
15727 void
15728 PonderNextMoveEvent (int newState)
15729 {
15730     if (newState == appData.ponderNextMove) return;
15731     if (gameMode == EditPosition) EditPositionDone(TRUE);
15732     if (newState) {
15733         SendToProgram("hard\n", &first);
15734         if (gameMode == TwoMachinesPlay) {
15735             SendToProgram("hard\n", &second);
15736         }
15737     } else {
15738         SendToProgram("easy\n", &first);
15739         thinkOutput[0] = NULLCHAR;
15740         if (gameMode == TwoMachinesPlay) {
15741             SendToProgram("easy\n", &second);
15742         }
15743     }
15744     appData.ponderNextMove = newState;
15745 }
15746
15747 void
15748 NewSettingEvent (int option, int *feature, char *command, int value)
15749 {
15750     char buf[MSG_SIZ];
15751
15752     if (gameMode == EditPosition) EditPositionDone(TRUE);
15753     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15754     if(feature == NULL || *feature) SendToProgram(buf, &first);
15755     if (gameMode == TwoMachinesPlay) {
15756         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15757     }
15758 }
15759
15760 void
15761 ShowThinkingEvent ()
15762 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15763 {
15764     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15765     int newState = appData.showThinking
15766         // [HGM] thinking: other features now need thinking output as well
15767         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15768
15769     if (oldState == newState) return;
15770     oldState = newState;
15771     if (gameMode == EditPosition) EditPositionDone(TRUE);
15772     if (oldState) {
15773         SendToProgram("post\n", &first);
15774         if (gameMode == TwoMachinesPlay) {
15775             SendToProgram("post\n", &second);
15776         }
15777     } else {
15778         SendToProgram("nopost\n", &first);
15779         thinkOutput[0] = NULLCHAR;
15780         if (gameMode == TwoMachinesPlay) {
15781             SendToProgram("nopost\n", &second);
15782         }
15783     }
15784 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15785 }
15786
15787 void
15788 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15789 {
15790   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15791   if (pr == NoProc) return;
15792   AskQuestion(title, question, replyPrefix, pr);
15793 }
15794
15795 void
15796 TypeInEvent (char firstChar)
15797 {
15798     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15799         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15800         gameMode == AnalyzeMode || gameMode == EditGame || 
15801         gameMode == EditPosition || gameMode == IcsExamining ||
15802         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15803         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15804                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15805                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15806         gameMode == Training) PopUpMoveDialog(firstChar);
15807 }
15808
15809 void
15810 TypeInDoneEvent (char *move)
15811 {
15812         Board board;
15813         int n, fromX, fromY, toX, toY;
15814         char promoChar;
15815         ChessMove moveType;
15816
15817         // [HGM] FENedit
15818         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15819                 EditPositionPasteFEN(move);
15820                 return;
15821         }
15822         // [HGM] movenum: allow move number to be typed in any mode
15823         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15824           ToNrEvent(2*n-1);
15825           return;
15826         }
15827         // undocumented kludge: allow command-line option to be typed in!
15828         // (potentially fatal, and does not implement the effect of the option.)
15829         // should only be used for options that are values on which future decisions will be made,
15830         // and definitely not on options that would be used during initialization.
15831         if(strstr(move, "!!! -") == move) {
15832             ParseArgsFromString(move+4);
15833             return;
15834         }
15835
15836       if (gameMode != EditGame && currentMove != forwardMostMove && 
15837         gameMode != Training) {
15838         DisplayMoveError(_("Displayed move is not current"));
15839       } else {
15840         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15841           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15842         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15843         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15844           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15845           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15846         } else {
15847           DisplayMoveError(_("Could not parse move"));
15848         }
15849       }
15850 }
15851
15852 void
15853 DisplayMove (int moveNumber)
15854 {
15855     char message[MSG_SIZ];
15856     char res[MSG_SIZ];
15857     char cpThinkOutput[MSG_SIZ];
15858
15859     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15860
15861     if (moveNumber == forwardMostMove - 1 ||
15862         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15863
15864         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15865
15866         if (strchr(cpThinkOutput, '\n')) {
15867             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15868         }
15869     } else {
15870         *cpThinkOutput = NULLCHAR;
15871     }
15872
15873     /* [AS] Hide thinking from human user */
15874     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15875         *cpThinkOutput = NULLCHAR;
15876         if( thinkOutput[0] != NULLCHAR ) {
15877             int i;
15878
15879             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15880                 cpThinkOutput[i] = '.';
15881             }
15882             cpThinkOutput[i] = NULLCHAR;
15883             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15884         }
15885     }
15886
15887     if (moveNumber == forwardMostMove - 1 &&
15888         gameInfo.resultDetails != NULL) {
15889         if (gameInfo.resultDetails[0] == NULLCHAR) {
15890           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15891         } else {
15892           snprintf(res, MSG_SIZ, " {%s} %s",
15893                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15894         }
15895     } else {
15896         res[0] = NULLCHAR;
15897     }
15898
15899     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15900         DisplayMessage(res, cpThinkOutput);
15901     } else {
15902       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15903                 WhiteOnMove(moveNumber) ? " " : ".. ",
15904                 parseList[moveNumber], res);
15905         DisplayMessage(message, cpThinkOutput);
15906     }
15907 }
15908
15909 void
15910 DisplayComment (int moveNumber, char *text)
15911 {
15912     char title[MSG_SIZ];
15913
15914     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15915       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15916     } else {
15917       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15918               WhiteOnMove(moveNumber) ? " " : ".. ",
15919               parseList[moveNumber]);
15920     }
15921     if (text != NULL && (appData.autoDisplayComment || commentUp))
15922         CommentPopUp(title, text);
15923 }
15924
15925 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15926  * might be busy thinking or pondering.  It can be omitted if your
15927  * gnuchess is configured to stop thinking immediately on any user
15928  * input.  However, that gnuchess feature depends on the FIONREAD
15929  * ioctl, which does not work properly on some flavors of Unix.
15930  */
15931 void
15932 Attention (ChessProgramState *cps)
15933 {
15934 #if ATTENTION
15935     if (!cps->useSigint) return;
15936     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15937     switch (gameMode) {
15938       case MachinePlaysWhite:
15939       case MachinePlaysBlack:
15940       case TwoMachinesPlay:
15941       case IcsPlayingWhite:
15942       case IcsPlayingBlack:
15943       case AnalyzeMode:
15944       case AnalyzeFile:
15945         /* Skip if we know it isn't thinking */
15946         if (!cps->maybeThinking) return;
15947         if (appData.debugMode)
15948           fprintf(debugFP, "Interrupting %s\n", cps->which);
15949         InterruptChildProcess(cps->pr);
15950         cps->maybeThinking = FALSE;
15951         break;
15952       default:
15953         break;
15954     }
15955 #endif /*ATTENTION*/
15956 }
15957
15958 int
15959 CheckFlags ()
15960 {
15961     if (whiteTimeRemaining <= 0) {
15962         if (!whiteFlag) {
15963             whiteFlag = TRUE;
15964             if (appData.icsActive) {
15965                 if (appData.autoCallFlag &&
15966                     gameMode == IcsPlayingBlack && !blackFlag) {
15967                   SendToICS(ics_prefix);
15968                   SendToICS("flag\n");
15969                 }
15970             } else {
15971                 if (blackFlag) {
15972                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15973                 } else {
15974                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15975                     if (appData.autoCallFlag) {
15976                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15977                         return TRUE;
15978                     }
15979                 }
15980             }
15981         }
15982     }
15983     if (blackTimeRemaining <= 0) {
15984         if (!blackFlag) {
15985             blackFlag = TRUE;
15986             if (appData.icsActive) {
15987                 if (appData.autoCallFlag &&
15988                     gameMode == IcsPlayingWhite && !whiteFlag) {
15989                   SendToICS(ics_prefix);
15990                   SendToICS("flag\n");
15991                 }
15992             } else {
15993                 if (whiteFlag) {
15994                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15995                 } else {
15996                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15997                     if (appData.autoCallFlag) {
15998                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15999                         return TRUE;
16000                     }
16001                 }
16002             }
16003         }
16004     }
16005     return FALSE;
16006 }
16007
16008 void
16009 CheckTimeControl ()
16010 {
16011     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16012         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16013
16014     /*
16015      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16016      */
16017     if ( !WhiteOnMove(forwardMostMove) ) {
16018         /* White made time control */
16019         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16020         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16021         /* [HGM] time odds: correct new time quota for time odds! */
16022                                             / WhitePlayer()->timeOdds;
16023         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16024     } else {
16025         lastBlack -= blackTimeRemaining;
16026         /* Black made time control */
16027         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16028                                             / WhitePlayer()->other->timeOdds;
16029         lastWhite = whiteTimeRemaining;
16030     }
16031 }
16032
16033 void
16034 DisplayBothClocks ()
16035 {
16036     int wom = gameMode == EditPosition ?
16037       !blackPlaysFirst : WhiteOnMove(currentMove);
16038     DisplayWhiteClock(whiteTimeRemaining, wom);
16039     DisplayBlackClock(blackTimeRemaining, !wom);
16040 }
16041
16042
16043 /* Timekeeping seems to be a portability nightmare.  I think everyone
16044    has ftime(), but I'm really not sure, so I'm including some ifdefs
16045    to use other calls if you don't.  Clocks will be less accurate if
16046    you have neither ftime nor gettimeofday.
16047 */
16048
16049 /* VS 2008 requires the #include outside of the function */
16050 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16051 #include <sys/timeb.h>
16052 #endif
16053
16054 /* Get the current time as a TimeMark */
16055 void
16056 GetTimeMark (TimeMark *tm)
16057 {
16058 #if HAVE_GETTIMEOFDAY
16059
16060     struct timeval timeVal;
16061     struct timezone timeZone;
16062
16063     gettimeofday(&timeVal, &timeZone);
16064     tm->sec = (long) timeVal.tv_sec;
16065     tm->ms = (int) (timeVal.tv_usec / 1000L);
16066
16067 #else /*!HAVE_GETTIMEOFDAY*/
16068 #if HAVE_FTIME
16069
16070 // include <sys/timeb.h> / moved to just above start of function
16071     struct timeb timeB;
16072
16073     ftime(&timeB);
16074     tm->sec = (long) timeB.time;
16075     tm->ms = (int) timeB.millitm;
16076
16077 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16078     tm->sec = (long) time(NULL);
16079     tm->ms = 0;
16080 #endif
16081 #endif
16082 }
16083
16084 /* Return the difference in milliseconds between two
16085    time marks.  We assume the difference will fit in a long!
16086 */
16087 long
16088 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16089 {
16090     return 1000L*(tm2->sec - tm1->sec) +
16091            (long) (tm2->ms - tm1->ms);
16092 }
16093
16094
16095 /*
16096  * Code to manage the game clocks.
16097  *
16098  * In tournament play, black starts the clock and then white makes a move.
16099  * We give the human user a slight advantage if he is playing white---the
16100  * clocks don't run until he makes his first move, so it takes zero time.
16101  * Also, we don't account for network lag, so we could get out of sync
16102  * with GNU Chess's clock -- but then, referees are always right.
16103  */
16104
16105 static TimeMark tickStartTM;
16106 static long intendedTickLength;
16107
16108 long
16109 NextTickLength (long timeRemaining)
16110 {
16111     long nominalTickLength, nextTickLength;
16112
16113     if (timeRemaining > 0L && timeRemaining <= 10000L)
16114       nominalTickLength = 100L;
16115     else
16116       nominalTickLength = 1000L;
16117     nextTickLength = timeRemaining % nominalTickLength;
16118     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16119
16120     return nextTickLength;
16121 }
16122
16123 /* Adjust clock one minute up or down */
16124 void
16125 AdjustClock (Boolean which, int dir)
16126 {
16127     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16128     if(which) blackTimeRemaining += 60000*dir;
16129     else      whiteTimeRemaining += 60000*dir;
16130     DisplayBothClocks();
16131     adjustedClock = TRUE;
16132 }
16133
16134 /* Stop clocks and reset to a fresh time control */
16135 void
16136 ResetClocks ()
16137 {
16138     (void) StopClockTimer();
16139     if (appData.icsActive) {
16140         whiteTimeRemaining = blackTimeRemaining = 0;
16141     } else if (searchTime) {
16142         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16143         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16144     } else { /* [HGM] correct new time quote for time odds */
16145         whiteTC = blackTC = fullTimeControlString;
16146         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16147         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16148     }
16149     if (whiteFlag || blackFlag) {
16150         DisplayTitle("");
16151         whiteFlag = blackFlag = FALSE;
16152     }
16153     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16154     DisplayBothClocks();
16155     adjustedClock = FALSE;
16156 }
16157
16158 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16159
16160 /* Decrement running clock by amount of time that has passed */
16161 void
16162 DecrementClocks ()
16163 {
16164     long timeRemaining;
16165     long lastTickLength, fudge;
16166     TimeMark now;
16167
16168     if (!appData.clockMode) return;
16169     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16170
16171     GetTimeMark(&now);
16172
16173     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16174
16175     /* Fudge if we woke up a little too soon */
16176     fudge = intendedTickLength - lastTickLength;
16177     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16178
16179     if (WhiteOnMove(forwardMostMove)) {
16180         if(whiteNPS >= 0) lastTickLength = 0;
16181         timeRemaining = whiteTimeRemaining -= lastTickLength;
16182         if(timeRemaining < 0 && !appData.icsActive) {
16183             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16184             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16185                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16186                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16187             }
16188         }
16189         DisplayWhiteClock(whiteTimeRemaining - fudge,
16190                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16191     } else {
16192         if(blackNPS >= 0) lastTickLength = 0;
16193         timeRemaining = blackTimeRemaining -= lastTickLength;
16194         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16195             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16196             if(suddenDeath) {
16197                 blackStartMove = forwardMostMove;
16198                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16199             }
16200         }
16201         DisplayBlackClock(blackTimeRemaining - fudge,
16202                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16203     }
16204     if (CheckFlags()) return;
16205
16206     tickStartTM = now;
16207     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16208     StartClockTimer(intendedTickLength);
16209
16210     /* if the time remaining has fallen below the alarm threshold, sound the
16211      * alarm. if the alarm has sounded and (due to a takeback or time control
16212      * with increment) the time remaining has increased to a level above the
16213      * threshold, reset the alarm so it can sound again.
16214      */
16215
16216     if (appData.icsActive && appData.icsAlarm) {
16217
16218         /* make sure we are dealing with the user's clock */
16219         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16220                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16221            )) return;
16222
16223         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16224             alarmSounded = FALSE;
16225         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16226             PlayAlarmSound();
16227             alarmSounded = TRUE;
16228         }
16229     }
16230 }
16231
16232
16233 /* A player has just moved, so stop the previously running
16234    clock and (if in clock mode) start the other one.
16235    We redisplay both clocks in case we're in ICS mode, because
16236    ICS gives us an update to both clocks after every move.
16237    Note that this routine is called *after* forwardMostMove
16238    is updated, so the last fractional tick must be subtracted
16239    from the color that is *not* on move now.
16240 */
16241 void
16242 SwitchClocks (int newMoveNr)
16243 {
16244     long lastTickLength;
16245     TimeMark now;
16246     int flagged = FALSE;
16247
16248     GetTimeMark(&now);
16249
16250     if (StopClockTimer() && appData.clockMode) {
16251         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16252         if (!WhiteOnMove(forwardMostMove)) {
16253             if(blackNPS >= 0) lastTickLength = 0;
16254             blackTimeRemaining -= lastTickLength;
16255            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16256 //         if(pvInfoList[forwardMostMove].time == -1)
16257                  pvInfoList[forwardMostMove].time =               // use GUI time
16258                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16259         } else {
16260            if(whiteNPS >= 0) lastTickLength = 0;
16261            whiteTimeRemaining -= lastTickLength;
16262            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16263 //         if(pvInfoList[forwardMostMove].time == -1)
16264                  pvInfoList[forwardMostMove].time =
16265                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16266         }
16267         flagged = CheckFlags();
16268     }
16269     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16270     CheckTimeControl();
16271
16272     if (flagged || !appData.clockMode) return;
16273
16274     switch (gameMode) {
16275       case MachinePlaysBlack:
16276       case MachinePlaysWhite:
16277       case BeginningOfGame:
16278         if (pausing) return;
16279         break;
16280
16281       case EditGame:
16282       case PlayFromGameFile:
16283       case IcsExamining:
16284         return;
16285
16286       default:
16287         break;
16288     }
16289
16290     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16291         if(WhiteOnMove(forwardMostMove))
16292              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16293         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16294     }
16295
16296     tickStartTM = now;
16297     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16298       whiteTimeRemaining : blackTimeRemaining);
16299     StartClockTimer(intendedTickLength);
16300 }
16301
16302
16303 /* Stop both clocks */
16304 void
16305 StopClocks ()
16306 {
16307     long lastTickLength;
16308     TimeMark now;
16309
16310     if (!StopClockTimer()) return;
16311     if (!appData.clockMode) return;
16312
16313     GetTimeMark(&now);
16314
16315     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16316     if (WhiteOnMove(forwardMostMove)) {
16317         if(whiteNPS >= 0) lastTickLength = 0;
16318         whiteTimeRemaining -= lastTickLength;
16319         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16320     } else {
16321         if(blackNPS >= 0) lastTickLength = 0;
16322         blackTimeRemaining -= lastTickLength;
16323         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16324     }
16325     CheckFlags();
16326 }
16327
16328 /* Start clock of player on move.  Time may have been reset, so
16329    if clock is already running, stop and restart it. */
16330 void
16331 StartClocks ()
16332 {
16333     (void) StopClockTimer(); /* in case it was running already */
16334     DisplayBothClocks();
16335     if (CheckFlags()) return;
16336
16337     if (!appData.clockMode) return;
16338     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16339
16340     GetTimeMark(&tickStartTM);
16341     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16342       whiteTimeRemaining : blackTimeRemaining);
16343
16344    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16345     whiteNPS = blackNPS = -1;
16346     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16347        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16348         whiteNPS = first.nps;
16349     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16350        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16351         blackNPS = first.nps;
16352     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16353         whiteNPS = second.nps;
16354     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16355         blackNPS = second.nps;
16356     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16357
16358     StartClockTimer(intendedTickLength);
16359 }
16360
16361 char *
16362 TimeString (long ms)
16363 {
16364     long second, minute, hour, day;
16365     char *sign = "";
16366     static char buf[32];
16367
16368     if (ms > 0 && ms <= 9900) {
16369       /* convert milliseconds to tenths, rounding up */
16370       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16371
16372       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16373       return buf;
16374     }
16375
16376     /* convert milliseconds to seconds, rounding up */
16377     /* use floating point to avoid strangeness of integer division
16378        with negative dividends on many machines */
16379     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16380
16381     if (second < 0) {
16382         sign = "-";
16383         second = -second;
16384     }
16385
16386     day = second / (60 * 60 * 24);
16387     second = second % (60 * 60 * 24);
16388     hour = second / (60 * 60);
16389     second = second % (60 * 60);
16390     minute = second / 60;
16391     second = second % 60;
16392
16393     if (day > 0)
16394       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16395               sign, day, hour, minute, second);
16396     else if (hour > 0)
16397       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16398     else
16399       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16400
16401     return buf;
16402 }
16403
16404
16405 /*
16406  * This is necessary because some C libraries aren't ANSI C compliant yet.
16407  */
16408 char *
16409 StrStr (char *string, char *match)
16410 {
16411     int i, length;
16412
16413     length = strlen(match);
16414
16415     for (i = strlen(string) - length; i >= 0; i--, string++)
16416       if (!strncmp(match, string, length))
16417         return string;
16418
16419     return NULL;
16420 }
16421
16422 char *
16423 StrCaseStr (char *string, char *match)
16424 {
16425     int i, j, length;
16426
16427     length = strlen(match);
16428
16429     for (i = strlen(string) - length; i >= 0; i--, string++) {
16430         for (j = 0; j < length; j++) {
16431             if (ToLower(match[j]) != ToLower(string[j]))
16432               break;
16433         }
16434         if (j == length) return string;
16435     }
16436
16437     return NULL;
16438 }
16439
16440 #ifndef _amigados
16441 int
16442 StrCaseCmp (char *s1, char *s2)
16443 {
16444     char c1, c2;
16445
16446     for (;;) {
16447         c1 = ToLower(*s1++);
16448         c2 = ToLower(*s2++);
16449         if (c1 > c2) return 1;
16450         if (c1 < c2) return -1;
16451         if (c1 == NULLCHAR) return 0;
16452     }
16453 }
16454
16455
16456 int
16457 ToLower (int c)
16458 {
16459     return isupper(c) ? tolower(c) : c;
16460 }
16461
16462
16463 int
16464 ToUpper (int c)
16465 {
16466     return islower(c) ? toupper(c) : c;
16467 }
16468 #endif /* !_amigados    */
16469
16470 char *
16471 StrSave (char *s)
16472 {
16473   char *ret;
16474
16475   if ((ret = (char *) malloc(strlen(s) + 1)))
16476     {
16477       safeStrCpy(ret, s, strlen(s)+1);
16478     }
16479   return ret;
16480 }
16481
16482 char *
16483 StrSavePtr (char *s, char **savePtr)
16484 {
16485     if (*savePtr) {
16486         free(*savePtr);
16487     }
16488     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16489       safeStrCpy(*savePtr, s, strlen(s)+1);
16490     }
16491     return(*savePtr);
16492 }
16493
16494 char *
16495 PGNDate ()
16496 {
16497     time_t clock;
16498     struct tm *tm;
16499     char buf[MSG_SIZ];
16500
16501     clock = time((time_t *)NULL);
16502     tm = localtime(&clock);
16503     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16504             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16505     return StrSave(buf);
16506 }
16507
16508
16509 char *
16510 PositionToFEN (int move, char *overrideCastling)
16511 {
16512     int i, j, fromX, fromY, toX, toY;
16513     int whiteToPlay;
16514     char buf[MSG_SIZ];
16515     char *p, *q;
16516     int emptycount;
16517     ChessSquare piece;
16518
16519     whiteToPlay = (gameMode == EditPosition) ?
16520       !blackPlaysFirst : (move % 2 == 0);
16521     p = buf;
16522
16523     /* Piece placement data */
16524     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16525         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16526         emptycount = 0;
16527         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16528             if (boards[move][i][j] == EmptySquare) {
16529                 emptycount++;
16530             } else { ChessSquare piece = boards[move][i][j];
16531                 if (emptycount > 0) {
16532                     if(emptycount<10) /* [HGM] can be >= 10 */
16533                         *p++ = '0' + emptycount;
16534                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16535                     emptycount = 0;
16536                 }
16537                 if(PieceToChar(piece) == '+') {
16538                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16539                     *p++ = '+';
16540                     piece = (ChessSquare)(DEMOTED piece);
16541                 }
16542                 *p++ = PieceToChar(piece);
16543                 if(p[-1] == '~') {
16544                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16545                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16546                     *p++ = '~';
16547                 }
16548             }
16549         }
16550         if (emptycount > 0) {
16551             if(emptycount<10) /* [HGM] can be >= 10 */
16552                 *p++ = '0' + emptycount;
16553             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16554             emptycount = 0;
16555         }
16556         *p++ = '/';
16557     }
16558     *(p - 1) = ' ';
16559
16560     /* [HGM] print Crazyhouse or Shogi holdings */
16561     if( gameInfo.holdingsWidth ) {
16562         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16563         q = p;
16564         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16565             piece = boards[move][i][BOARD_WIDTH-1];
16566             if( piece != EmptySquare )
16567               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16568                   *p++ = PieceToChar(piece);
16569         }
16570         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16571             piece = boards[move][BOARD_HEIGHT-i-1][0];
16572             if( piece != EmptySquare )
16573               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16574                   *p++ = PieceToChar(piece);
16575         }
16576
16577         if( q == p ) *p++ = '-';
16578         *p++ = ']';
16579         *p++ = ' ';
16580     }
16581
16582     /* Active color */
16583     *p++ = whiteToPlay ? 'w' : 'b';
16584     *p++ = ' ';
16585
16586   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16587     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16588   } else {
16589   if(nrCastlingRights) {
16590      q = p;
16591      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16592        /* [HGM] write directly from rights */
16593            if(boards[move][CASTLING][2] != NoRights &&
16594               boards[move][CASTLING][0] != NoRights   )
16595                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16596            if(boards[move][CASTLING][2] != NoRights &&
16597               boards[move][CASTLING][1] != NoRights   )
16598                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16599            if(boards[move][CASTLING][5] != NoRights &&
16600               boards[move][CASTLING][3] != NoRights   )
16601                 *p++ = boards[move][CASTLING][3] + AAA;
16602            if(boards[move][CASTLING][5] != NoRights &&
16603               boards[move][CASTLING][4] != NoRights   )
16604                 *p++ = boards[move][CASTLING][4] + AAA;
16605      } else {
16606
16607         /* [HGM] write true castling rights */
16608         if( nrCastlingRights == 6 ) {
16609             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16610                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16611             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16612                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16613             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16614                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16615             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16616                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16617         }
16618      }
16619      if (q == p) *p++ = '-'; /* No castling rights */
16620      *p++ = ' ';
16621   }
16622
16623   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16624      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16625     /* En passant target square */
16626     if (move > backwardMostMove) {
16627         fromX = moveList[move - 1][0] - AAA;
16628         fromY = moveList[move - 1][1] - ONE;
16629         toX = moveList[move - 1][2] - AAA;
16630         toY = moveList[move - 1][3] - ONE;
16631         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16632             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16633             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16634             fromX == toX) {
16635             /* 2-square pawn move just happened */
16636             *p++ = toX + AAA;
16637             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16638         } else {
16639             *p++ = '-';
16640         }
16641     } else if(move == backwardMostMove) {
16642         // [HGM] perhaps we should always do it like this, and forget the above?
16643         if((signed char)boards[move][EP_STATUS] >= 0) {
16644             *p++ = boards[move][EP_STATUS] + AAA;
16645             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16646         } else {
16647             *p++ = '-';
16648         }
16649     } else {
16650         *p++ = '-';
16651     }
16652     *p++ = ' ';
16653   }
16654   }
16655
16656     /* [HGM] find reversible plies */
16657     {   int i = 0, j=move;
16658
16659         if (appData.debugMode) { int k;
16660             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16661             for(k=backwardMostMove; k<=forwardMostMove; k++)
16662                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16663
16664         }
16665
16666         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16667         if( j == backwardMostMove ) i += initialRulePlies;
16668         sprintf(p, "%d ", i);
16669         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16670     }
16671     /* Fullmove number */
16672     sprintf(p, "%d", (move / 2) + 1);
16673
16674     return StrSave(buf);
16675 }
16676
16677 Boolean
16678 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16679 {
16680     int i, j;
16681     char *p, c;
16682     int emptycount;
16683     ChessSquare piece;
16684
16685     p = fen;
16686
16687     /* [HGM] by default clear Crazyhouse holdings, if present */
16688     if(gameInfo.holdingsWidth) {
16689        for(i=0; i<BOARD_HEIGHT; i++) {
16690            board[i][0]             = EmptySquare; /* black holdings */
16691            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16692            board[i][1]             = (ChessSquare) 0; /* black counts */
16693            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16694        }
16695     }
16696
16697     /* Piece placement data */
16698     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16699         j = 0;
16700         for (;;) {
16701             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16702                 if (*p == '/') p++;
16703                 emptycount = gameInfo.boardWidth - j;
16704                 while (emptycount--)
16705                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16706                 break;
16707 #if(BOARD_FILES >= 10)
16708             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16709                 p++; emptycount=10;
16710                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16711                 while (emptycount--)
16712                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16713 #endif
16714             } else if (isdigit(*p)) {
16715                 emptycount = *p++ - '0';
16716                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16717                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16718                 while (emptycount--)
16719                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16720             } else if (*p == '+' || isalpha(*p)) {
16721                 if (j >= gameInfo.boardWidth) return FALSE;
16722                 if(*p=='+') {
16723                     piece = CharToPiece(*++p);
16724                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16725                     piece = (ChessSquare) (PROMOTED piece ); p++;
16726                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16727                 } else piece = CharToPiece(*p++);
16728
16729                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16730                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16731                     piece = (ChessSquare) (PROMOTED piece);
16732                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16733                     p++;
16734                 }
16735                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16736             } else {
16737                 return FALSE;
16738             }
16739         }
16740     }
16741     while (*p == '/' || *p == ' ') p++;
16742
16743     /* [HGM] look for Crazyhouse holdings here */
16744     while(*p==' ') p++;
16745     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16746         if(*p == '[') p++;
16747         if(*p == '-' ) p++; /* empty holdings */ else {
16748             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16749             /* if we would allow FEN reading to set board size, we would   */
16750             /* have to add holdings and shift the board read so far here   */
16751             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16752                 p++;
16753                 if((int) piece >= (int) BlackPawn ) {
16754                     i = (int)piece - (int)BlackPawn;
16755                     i = PieceToNumber((ChessSquare)i);
16756                     if( i >= gameInfo.holdingsSize ) return FALSE;
16757                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16758                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16759                 } else {
16760                     i = (int)piece - (int)WhitePawn;
16761                     i = PieceToNumber((ChessSquare)i);
16762                     if( i >= gameInfo.holdingsSize ) return FALSE;
16763                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16764                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16765                 }
16766             }
16767         }
16768         if(*p == ']') p++;
16769     }
16770
16771     while(*p == ' ') p++;
16772
16773     /* Active color */
16774     c = *p++;
16775     if(appData.colorNickNames) {
16776       if( c == appData.colorNickNames[0] ) c = 'w'; else
16777       if( c == appData.colorNickNames[1] ) c = 'b';
16778     }
16779     switch (c) {
16780       case 'w':
16781         *blackPlaysFirst = FALSE;
16782         break;
16783       case 'b':
16784         *blackPlaysFirst = TRUE;
16785         break;
16786       default:
16787         return FALSE;
16788     }
16789
16790     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16791     /* return the extra info in global variiables             */
16792
16793     /* set defaults in case FEN is incomplete */
16794     board[EP_STATUS] = EP_UNKNOWN;
16795     for(i=0; i<nrCastlingRights; i++ ) {
16796         board[CASTLING][i] =
16797             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16798     }   /* assume possible unless obviously impossible */
16799     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16800     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16801     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16802                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16803     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16804     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16805     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16806                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16807     FENrulePlies = 0;
16808
16809     while(*p==' ') p++;
16810     if(nrCastlingRights) {
16811       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16812           /* castling indicator present, so default becomes no castlings */
16813           for(i=0; i<nrCastlingRights; i++ ) {
16814                  board[CASTLING][i] = NoRights;
16815           }
16816       }
16817       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16818              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16819              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16820              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16821         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16822
16823         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16824             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16825             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16826         }
16827         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16828             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16829         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16830                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16831         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16832                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16833         switch(c) {
16834           case'K':
16835               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16836               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16837               board[CASTLING][2] = whiteKingFile;
16838               break;
16839           case'Q':
16840               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16841               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16842               board[CASTLING][2] = whiteKingFile;
16843               break;
16844           case'k':
16845               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16846               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16847               board[CASTLING][5] = blackKingFile;
16848               break;
16849           case'q':
16850               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16851               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16852               board[CASTLING][5] = blackKingFile;
16853           case '-':
16854               break;
16855           default: /* FRC castlings */
16856               if(c >= 'a') { /* black rights */
16857                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16858                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16859                   if(i == BOARD_RGHT) break;
16860                   board[CASTLING][5] = i;
16861                   c -= AAA;
16862                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16863                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16864                   if(c > i)
16865                       board[CASTLING][3] = c;
16866                   else
16867                       board[CASTLING][4] = c;
16868               } else { /* white rights */
16869                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16870                     if(board[0][i] == WhiteKing) break;
16871                   if(i == BOARD_RGHT) break;
16872                   board[CASTLING][2] = i;
16873                   c -= AAA - 'a' + 'A';
16874                   if(board[0][c] >= WhiteKing) break;
16875                   if(c > i)
16876                       board[CASTLING][0] = c;
16877                   else
16878                       board[CASTLING][1] = c;
16879               }
16880         }
16881       }
16882       for(i=0; i<nrCastlingRights; i++)
16883         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16884     if (appData.debugMode) {
16885         fprintf(debugFP, "FEN castling rights:");
16886         for(i=0; i<nrCastlingRights; i++)
16887         fprintf(debugFP, " %d", board[CASTLING][i]);
16888         fprintf(debugFP, "\n");
16889     }
16890
16891       while(*p==' ') p++;
16892     }
16893
16894     /* read e.p. field in games that know e.p. capture */
16895     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16896        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16897       if(*p=='-') {
16898         p++; board[EP_STATUS] = EP_NONE;
16899       } else {
16900          char c = *p++ - AAA;
16901
16902          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16903          if(*p >= '0' && *p <='9') p++;
16904          board[EP_STATUS] = c;
16905       }
16906     }
16907
16908
16909     if(sscanf(p, "%d", &i) == 1) {
16910         FENrulePlies = i; /* 50-move ply counter */
16911         /* (The move number is still ignored)    */
16912     }
16913
16914     return TRUE;
16915 }
16916
16917 void
16918 EditPositionPasteFEN (char *fen)
16919 {
16920   if (fen != NULL) {
16921     Board initial_position;
16922
16923     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16924       DisplayError(_("Bad FEN position in clipboard"), 0);
16925       return ;
16926     } else {
16927       int savedBlackPlaysFirst = blackPlaysFirst;
16928       EditPositionEvent();
16929       blackPlaysFirst = savedBlackPlaysFirst;
16930       CopyBoard(boards[0], initial_position);
16931       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16932       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16933       DisplayBothClocks();
16934       DrawPosition(FALSE, boards[currentMove]);
16935     }
16936   }
16937 }
16938
16939 static char cseq[12] = "\\   ";
16940
16941 Boolean
16942 set_cont_sequence (char *new_seq)
16943 {
16944     int len;
16945     Boolean ret;
16946
16947     // handle bad attempts to set the sequence
16948         if (!new_seq)
16949                 return 0; // acceptable error - no debug
16950
16951     len = strlen(new_seq);
16952     ret = (len > 0) && (len < sizeof(cseq));
16953     if (ret)
16954       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16955     else if (appData.debugMode)
16956       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16957     return ret;
16958 }
16959
16960 /*
16961     reformat a source message so words don't cross the width boundary.  internal
16962     newlines are not removed.  returns the wrapped size (no null character unless
16963     included in source message).  If dest is NULL, only calculate the size required
16964     for the dest buffer.  lp argument indicats line position upon entry, and it's
16965     passed back upon exit.
16966 */
16967 int
16968 wrap (char *dest, char *src, int count, int width, int *lp)
16969 {
16970     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16971
16972     cseq_len = strlen(cseq);
16973     old_line = line = *lp;
16974     ansi = len = clen = 0;
16975
16976     for (i=0; i < count; i++)
16977     {
16978         if (src[i] == '\033')
16979             ansi = 1;
16980
16981         // if we hit the width, back up
16982         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16983         {
16984             // store i & len in case the word is too long
16985             old_i = i, old_len = len;
16986
16987             // find the end of the last word
16988             while (i && src[i] != ' ' && src[i] != '\n')
16989             {
16990                 i--;
16991                 len--;
16992             }
16993
16994             // word too long?  restore i & len before splitting it
16995             if ((old_i-i+clen) >= width)
16996             {
16997                 i = old_i;
16998                 len = old_len;
16999             }
17000
17001             // extra space?
17002             if (i && src[i-1] == ' ')
17003                 len--;
17004
17005             if (src[i] != ' ' && src[i] != '\n')
17006             {
17007                 i--;
17008                 if (len)
17009                     len--;
17010             }
17011
17012             // now append the newline and continuation sequence
17013             if (dest)
17014                 dest[len] = '\n';
17015             len++;
17016             if (dest)
17017                 strncpy(dest+len, cseq, cseq_len);
17018             len += cseq_len;
17019             line = cseq_len;
17020             clen = cseq_len;
17021             continue;
17022         }
17023
17024         if (dest)
17025             dest[len] = src[i];
17026         len++;
17027         if (!ansi)
17028             line++;
17029         if (src[i] == '\n')
17030             line = 0;
17031         if (src[i] == 'm')
17032             ansi = 0;
17033     }
17034     if (dest && appData.debugMode)
17035     {
17036         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17037             count, width, line, len, *lp);
17038         show_bytes(debugFP, src, count);
17039         fprintf(debugFP, "\ndest: ");
17040         show_bytes(debugFP, dest, len);
17041         fprintf(debugFP, "\n");
17042     }
17043     *lp = dest ? line : old_line;
17044
17045     return len;
17046 }
17047
17048 // [HGM] vari: routines for shelving variations
17049 Boolean modeRestore = FALSE;
17050
17051 void
17052 PushInner (int firstMove, int lastMove)
17053 {
17054         int i, j, nrMoves = lastMove - firstMove;
17055
17056         // push current tail of game on stack
17057         savedResult[storedGames] = gameInfo.result;
17058         savedDetails[storedGames] = gameInfo.resultDetails;
17059         gameInfo.resultDetails = NULL;
17060         savedFirst[storedGames] = firstMove;
17061         savedLast [storedGames] = lastMove;
17062         savedFramePtr[storedGames] = framePtr;
17063         framePtr -= nrMoves; // reserve space for the boards
17064         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17065             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17066             for(j=0; j<MOVE_LEN; j++)
17067                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17068             for(j=0; j<2*MOVE_LEN; j++)
17069                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17070             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17071             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17072             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17073             pvInfoList[firstMove+i-1].depth = 0;
17074             commentList[framePtr+i] = commentList[firstMove+i];
17075             commentList[firstMove+i] = NULL;
17076         }
17077
17078         storedGames++;
17079         forwardMostMove = firstMove; // truncate game so we can start variation
17080 }
17081
17082 void
17083 PushTail (int firstMove, int lastMove)
17084 {
17085         if(appData.icsActive) { // only in local mode
17086                 forwardMostMove = currentMove; // mimic old ICS behavior
17087                 return;
17088         }
17089         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17090
17091         PushInner(firstMove, lastMove);
17092         if(storedGames == 1) GreyRevert(FALSE);
17093         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17094 }
17095
17096 void
17097 PopInner (Boolean annotate)
17098 {
17099         int i, j, nrMoves;
17100         char buf[8000], moveBuf[20];
17101
17102         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17103         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17104         nrMoves = savedLast[storedGames] - currentMove;
17105         if(annotate) {
17106                 int cnt = 10;
17107                 if(!WhiteOnMove(currentMove))
17108                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17109                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17110                 for(i=currentMove; i<forwardMostMove; i++) {
17111                         if(WhiteOnMove(i))
17112                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17113                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17114                         strcat(buf, moveBuf);
17115                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17116                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17117                 }
17118                 strcat(buf, ")");
17119         }
17120         for(i=1; i<=nrMoves; i++) { // copy last variation back
17121             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17122             for(j=0; j<MOVE_LEN; j++)
17123                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17124             for(j=0; j<2*MOVE_LEN; j++)
17125                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17126             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17127             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17128             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17129             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17130             commentList[currentMove+i] = commentList[framePtr+i];
17131             commentList[framePtr+i] = NULL;
17132         }
17133         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17134         framePtr = savedFramePtr[storedGames];
17135         gameInfo.result = savedResult[storedGames];
17136         if(gameInfo.resultDetails != NULL) {
17137             free(gameInfo.resultDetails);
17138       }
17139         gameInfo.resultDetails = savedDetails[storedGames];
17140         forwardMostMove = currentMove + nrMoves;
17141 }
17142
17143 Boolean
17144 PopTail (Boolean annotate)
17145 {
17146         if(appData.icsActive) return FALSE; // only in local mode
17147         if(!storedGames) return FALSE; // sanity
17148         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17149
17150         PopInner(annotate);
17151         if(currentMove < forwardMostMove) ForwardEvent(); else
17152         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17153
17154         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17155         return TRUE;
17156 }
17157
17158 void
17159 CleanupTail ()
17160 {       // remove all shelved variations
17161         int i;
17162         for(i=0; i<storedGames; i++) {
17163             if(savedDetails[i])
17164                 free(savedDetails[i]);
17165             savedDetails[i] = NULL;
17166         }
17167         for(i=framePtr; i<MAX_MOVES; i++) {
17168                 if(commentList[i]) free(commentList[i]);
17169                 commentList[i] = NULL;
17170         }
17171         framePtr = MAX_MOVES-1;
17172         storedGames = 0;
17173 }
17174
17175 void
17176 LoadVariation (int index, char *text)
17177 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17178         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17179         int level = 0, move;
17180
17181         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17182         // first find outermost bracketing variation
17183         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17184             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17185                 if(*p == '{') wait = '}'; else
17186                 if(*p == '[') wait = ']'; else
17187                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17188                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17189             }
17190             if(*p == wait) wait = NULLCHAR; // closing ]} found
17191             p++;
17192         }
17193         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17194         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17195         end[1] = NULLCHAR; // clip off comment beyond variation
17196         ToNrEvent(currentMove-1);
17197         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17198         // kludge: use ParsePV() to append variation to game
17199         move = currentMove;
17200         ParsePV(start, TRUE, TRUE);
17201         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17202         ClearPremoveHighlights();
17203         CommentPopDown();
17204         ToNrEvent(currentMove+1);
17205 }
17206