Detect engine exit during startup
[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, controlKey; // [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); p = engineName;
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] = gatingPiece;
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                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7064                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7065                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7066                     promoSweep = defaultPromoChoice;
7067                     selectFlag = 0; lastX = xPix; lastY = yPix;
7068                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7069                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7070                 }
7071                 if (appData.highlightDragging) {
7072                     SetHighlights(fromX, fromY, -1, -1);
7073                 }
7074             } else fromX = fromY = -1;
7075             return;
7076         }
7077     }
7078
7079     /* fromX != -1 */
7080     if (clickType == Press && gameMode != EditPosition) {
7081         ChessSquare fromP;
7082         ChessSquare toP;
7083         int frc;
7084
7085         // ignore off-board to clicks
7086         if(y < 0 || x < 0) return;
7087
7088         /* Check if clicking again on the same color piece */
7089         fromP = boards[currentMove][fromY][fromX];
7090         toP = boards[currentMove][y][x];
7091         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7092         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7093              WhitePawn <= toP && toP <= WhiteKing &&
7094              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7095              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7096             (BlackPawn <= fromP && fromP <= BlackKing &&
7097              BlackPawn <= toP && toP <= BlackKing &&
7098              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7099              !(fromP == BlackKing && toP == BlackRook && frc))) {
7100             /* Clicked again on same color piece -- changed his mind */
7101             second = (x == fromX && y == fromY);
7102             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7103                 second = FALSE; // first double-click rather than scond click
7104                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7105             }
7106             promoDefaultAltered = FALSE;
7107             MarkTargetSquares(1);
7108            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7109             if (appData.highlightDragging) {
7110                 SetHighlights(x, y, -1, -1);
7111             } else {
7112                 ClearHighlights();
7113             }
7114             if (OKToStartUserMove(x, y)) {
7115                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7116                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7117                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7118                  gatingPiece = boards[currentMove][fromY][fromX];
7119                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7120                 fromX = x;
7121                 fromY = y; dragging = 1;
7122                 MarkTargetSquares(0);
7123                 DragPieceBegin(xPix, yPix, FALSE);
7124                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7125                     promoSweep = defaultPromoChoice;
7126                     selectFlag = 0; lastX = xPix; lastY = yPix;
7127                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7128                 }
7129             }
7130            }
7131            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7132            second = FALSE; 
7133         }
7134         // ignore clicks on holdings
7135         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7136     }
7137
7138     if (clickType == Release && x == fromX && y == fromY) {
7139         DragPieceEnd(xPix, yPix); dragging = 0;
7140         if(clearFlag) {
7141             // a deferred attempt to click-click move an empty square on top of a piece
7142             boards[currentMove][y][x] = EmptySquare;
7143             ClearHighlights();
7144             DrawPosition(FALSE, boards[currentMove]);
7145             fromX = fromY = -1; clearFlag = 0;
7146             return;
7147         }
7148         if (appData.animateDragging) {
7149             /* Undo animation damage if any */
7150             DrawPosition(FALSE, NULL);
7151         }
7152         if (second) {
7153             /* Second up/down in same square; just abort move */
7154             second = 0;
7155             fromX = fromY = -1;
7156             gatingPiece = EmptySquare;
7157             ClearHighlights();
7158             gotPremove = 0;
7159             ClearPremoveHighlights();
7160         } else {
7161             /* First upclick in same square; start click-click mode */
7162             SetHighlights(x, y, -1, -1);
7163         }
7164         return;
7165     }
7166
7167     clearFlag = 0;
7168
7169     /* we now have a different from- and (possibly off-board) to-square */
7170     /* Completed move */
7171     toX = x;
7172     toY = y;
7173     saveAnimate = appData.animate;
7174     MarkTargetSquares(1);
7175     if (clickType == Press) {
7176         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7177             // must be Edit Position mode with empty-square selected
7178             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7179             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7180             return;
7181         }
7182         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7183             ChessSquare piece = boards[currentMove][fromY][fromX];
7184             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7185             promoSweep = defaultPromoChoice;
7186             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7187             selectFlag = 0; lastX = xPix; lastY = yPix;
7188             Sweep(0); // Pawn that is going to promote: preview promotion piece
7189             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7190             DrawPosition(FALSE, boards[currentMove]);
7191             return;
7192         }
7193         /* Finish clickclick move */
7194         if (appData.animate || appData.highlightLastMove) {
7195             SetHighlights(fromX, fromY, toX, toY);
7196         } else {
7197             ClearHighlights();
7198         }
7199     } else {
7200         /* Finish drag move */
7201         if (appData.highlightLastMove) {
7202             SetHighlights(fromX, fromY, toX, toY);
7203         } else {
7204             ClearHighlights();
7205         }
7206         DragPieceEnd(xPix, yPix); dragging = 0;
7207         /* Don't animate move and drag both */
7208         appData.animate = FALSE;
7209     }
7210
7211     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7212     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7213         ChessSquare piece = boards[currentMove][fromY][fromX];
7214         if(gameMode == EditPosition && piece != EmptySquare &&
7215            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7216             int n;
7217
7218             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7219                 n = PieceToNumber(piece - (int)BlackPawn);
7220                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7221                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7222                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7223             } else
7224             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7225                 n = PieceToNumber(piece);
7226                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7227                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7228                 boards[currentMove][n][BOARD_WIDTH-2]++;
7229             }
7230             boards[currentMove][fromY][fromX] = EmptySquare;
7231         }
7232         ClearHighlights();
7233         fromX = fromY = -1;
7234         DrawPosition(TRUE, boards[currentMove]);
7235         return;
7236     }
7237
7238     // off-board moves should not be highlighted
7239     if(x < 0 || y < 0) ClearHighlights();
7240
7241     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7242
7243     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7244         SetHighlights(fromX, fromY, toX, toY);
7245         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7246             // [HGM] super: promotion to captured piece selected from holdings
7247             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7248             promotionChoice = TRUE;
7249             // kludge follows to temporarily execute move on display, without promoting yet
7250             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7251             boards[currentMove][toY][toX] = p;
7252             DrawPosition(FALSE, boards[currentMove]);
7253             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7254             boards[currentMove][toY][toX] = q;
7255             DisplayMessage("Click in holdings to choose piece", "");
7256             return;
7257         }
7258         PromotionPopUp();
7259     } else {
7260         int oldMove = currentMove;
7261         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7262         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7263         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7264         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7265            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7266             DrawPosition(TRUE, boards[currentMove]);
7267         fromX = fromY = -1;
7268     }
7269     appData.animate = saveAnimate;
7270     if (appData.animate || appData.animateDragging) {
7271         /* Undo animation damage if needed */
7272         DrawPosition(FALSE, NULL);
7273     }
7274 }
7275
7276 int
7277 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7278 {   // front-end-free part taken out of PieceMenuPopup
7279     int whichMenu; int xSqr, ySqr;
7280
7281     if(seekGraphUp) { // [HGM] seekgraph
7282         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7283         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7284         return -2;
7285     }
7286
7287     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7288          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7289         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7290         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7291         if(action == Press)   {
7292             originalFlip = flipView;
7293             flipView = !flipView; // temporarily flip board to see game from partners perspective
7294             DrawPosition(TRUE, partnerBoard);
7295             DisplayMessage(partnerStatus, "");
7296             partnerUp = TRUE;
7297         } else if(action == Release) {
7298             flipView = originalFlip;
7299             DrawPosition(TRUE, boards[currentMove]);
7300             partnerUp = FALSE;
7301         }
7302         return -2;
7303     }
7304
7305     xSqr = EventToSquare(x, BOARD_WIDTH);
7306     ySqr = EventToSquare(y, BOARD_HEIGHT);
7307     if (action == Release) {
7308         if(pieceSweep != EmptySquare) {
7309             EditPositionMenuEvent(pieceSweep, toX, toY);
7310             pieceSweep = EmptySquare;
7311         } else UnLoadPV(); // [HGM] pv
7312     }
7313     if (action != Press) return -2; // return code to be ignored
7314     switch (gameMode) {
7315       case IcsExamining:
7316         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7317       case EditPosition:
7318         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7319         if (xSqr < 0 || ySqr < 0) return -1;
7320         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7321         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7322         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7323         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7324         NextPiece(0);
7325         return 2; // grab
7326       case IcsObserving:
7327         if(!appData.icsEngineAnalyze) return -1;
7328       case IcsPlayingWhite:
7329       case IcsPlayingBlack:
7330         if(!appData.zippyPlay) goto noZip;
7331       case AnalyzeMode:
7332       case AnalyzeFile:
7333       case MachinePlaysWhite:
7334       case MachinePlaysBlack:
7335       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7336         if (!appData.dropMenu) {
7337           LoadPV(x, y);
7338           return 2; // flag front-end to grab mouse events
7339         }
7340         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7341            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7342       case EditGame:
7343       noZip:
7344         if (xSqr < 0 || ySqr < 0) return -1;
7345         if (!appData.dropMenu || appData.testLegality &&
7346             gameInfo.variant != VariantBughouse &&
7347             gameInfo.variant != VariantCrazyhouse) return -1;
7348         whichMenu = 1; // drop menu
7349         break;
7350       default:
7351         return -1;
7352     }
7353
7354     if (((*fromX = xSqr) < 0) ||
7355         ((*fromY = ySqr) < 0)) {
7356         *fromX = *fromY = -1;
7357         return -1;
7358     }
7359     if (flipView)
7360       *fromX = BOARD_WIDTH - 1 - *fromX;
7361     else
7362       *fromY = BOARD_HEIGHT - 1 - *fromY;
7363
7364     return whichMenu;
7365 }
7366
7367 void
7368 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7369 {
7370 //    char * hint = lastHint;
7371     FrontEndProgramStats stats;
7372
7373     stats.which = cps == &first ? 0 : 1;
7374     stats.depth = cpstats->depth;
7375     stats.nodes = cpstats->nodes;
7376     stats.score = cpstats->score;
7377     stats.time = cpstats->time;
7378     stats.pv = cpstats->movelist;
7379     stats.hint = lastHint;
7380     stats.an_move_index = 0;
7381     stats.an_move_count = 0;
7382
7383     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7384         stats.hint = cpstats->move_name;
7385         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7386         stats.an_move_count = cpstats->nr_moves;
7387     }
7388
7389     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
7390
7391     SetProgramStats( &stats );
7392 }
7393
7394 void
7395 ClearEngineOutputPane (int which)
7396 {
7397     static FrontEndProgramStats dummyStats;
7398     dummyStats.which = which;
7399     dummyStats.pv = "#";
7400     SetProgramStats( &dummyStats );
7401 }
7402
7403 #define MAXPLAYERS 500
7404
7405 char *
7406 TourneyStandings (int display)
7407 {
7408     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7409     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7410     char result, *p, *names[MAXPLAYERS];
7411
7412     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7413         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7414     names[0] = p = strdup(appData.participants);
7415     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7416
7417     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7418
7419     while(result = appData.results[nr]) {
7420         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7421         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7422         wScore = bScore = 0;
7423         switch(result) {
7424           case '+': wScore = 2; break;
7425           case '-': bScore = 2; break;
7426           case '=': wScore = bScore = 1; break;
7427           case ' ':
7428           case '*': return strdup("busy"); // tourney not finished
7429         }
7430         score[w] += wScore;
7431         score[b] += bScore;
7432         games[w]++;
7433         games[b]++;
7434         nr++;
7435     }
7436     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7437     for(w=0; w<nPlayers; w++) {
7438         bScore = -1;
7439         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7440         ranking[w] = b; points[w] = bScore; score[b] = -2;
7441     }
7442     p = malloc(nPlayers*34+1);
7443     for(w=0; w<nPlayers && w<display; w++)
7444         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7445     free(names[0]);
7446     return p;
7447 }
7448
7449 void
7450 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7451 {       // count all piece types
7452         int p, f, r;
7453         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7454         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7455         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7456                 p = board[r][f];
7457                 pCnt[p]++;
7458                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7459                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7460                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7461                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7462                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7463                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7464         }
7465 }
7466
7467 int
7468 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7469 {
7470         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7471         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7472
7473         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7474         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7475         if(myPawns == 2 && nMine == 3) // KPP
7476             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7477         if(myPawns == 1 && nMine == 2) // KP
7478             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7479         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7480             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7481         if(myPawns) return FALSE;
7482         if(pCnt[WhiteRook+side])
7483             return pCnt[BlackRook-side] ||
7484                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7485                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7486                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7487         if(pCnt[WhiteCannon+side]) {
7488             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7489             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7490         }
7491         if(pCnt[WhiteKnight+side])
7492             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7493         return FALSE;
7494 }
7495
7496 int
7497 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7498 {
7499         VariantClass v = gameInfo.variant;
7500
7501         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7502         if(v == VariantShatranj) return TRUE; // always winnable through baring
7503         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7504         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7505
7506         if(v == VariantXiangqi) {
7507                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7508
7509                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7510                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7511                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7512                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7513                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7514                 if(stale) // we have at least one last-rank P plus perhaps C
7515                     return majors // KPKX
7516                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7517                 else // KCA*E*
7518                     return pCnt[WhiteFerz+side] // KCAK
7519                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7520                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7521                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7522
7523         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7524                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7525
7526                 if(nMine == 1) return FALSE; // bare King
7527                 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
7528                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7529                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7530                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7531                 if(pCnt[WhiteKnight+side])
7532                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7533                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7534                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7535                 if(nBishops)
7536                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7537                 if(pCnt[WhiteAlfil+side])
7538                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7539                 if(pCnt[WhiteWazir+side])
7540                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7541         }
7542
7543         return TRUE;
7544 }
7545
7546 int
7547 CompareWithRights (Board b1, Board b2)
7548 {
7549     int rights = 0;
7550     if(!CompareBoards(b1, b2)) return FALSE;
7551     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7552     /* compare castling rights */
7553     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7554            rights++; /* King lost rights, while rook still had them */
7555     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7556         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7557            rights++; /* but at least one rook lost them */
7558     }
7559     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7560            rights++;
7561     if( b1[CASTLING][5] != NoRights ) {
7562         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7563            rights++;
7564     }
7565     return rights == 0;
7566 }
7567
7568 int
7569 Adjudicate (ChessProgramState *cps)
7570 {       // [HGM] some adjudications useful with buggy engines
7571         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7572         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7573         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7574         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7575         int k, count = 0; static int bare = 1;
7576         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7577         Boolean canAdjudicate = !appData.icsActive;
7578
7579         // most tests only when we understand the game, i.e. legality-checking on
7580             if( appData.testLegality )
7581             {   /* [HGM] Some more adjudications for obstinate engines */
7582                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7583                 static int moveCount = 6;
7584                 ChessMove result;
7585                 char *reason = NULL;
7586
7587                 /* Count what is on board. */
7588                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7589
7590                 /* Some material-based adjudications that have to be made before stalemate test */
7591                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7592                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7593                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7594                      if(canAdjudicate && appData.checkMates) {
7595                          if(engineOpponent)
7596                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7597                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7598                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7599                          return 1;
7600                      }
7601                 }
7602
7603                 /* Bare King in Shatranj (loses) or Losers (wins) */
7604                 if( nrW == 1 || nrB == 1) {
7605                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7606                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7607                      if(canAdjudicate && appData.checkMates) {
7608                          if(engineOpponent)
7609                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7610                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7611                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7612                          return 1;
7613                      }
7614                   } else
7615                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7616                   {    /* bare King */
7617                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7618                         if(canAdjudicate && appData.checkMates) {
7619                             /* but only adjudicate if adjudication enabled */
7620                             if(engineOpponent)
7621                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7622                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7623                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7624                             return 1;
7625                         }
7626                   }
7627                 } else bare = 1;
7628
7629
7630             // don't wait for engine to announce game end if we can judge ourselves
7631             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7632               case MT_CHECK:
7633                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7634                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7635                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7636                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7637                             checkCnt++;
7638                         if(checkCnt >= 2) {
7639                             reason = "Xboard adjudication: 3rd check";
7640                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7641                             break;
7642                         }
7643                     }
7644                 }
7645               case MT_NONE:
7646               default:
7647                 break;
7648               case MT_STALEMATE:
7649               case MT_STAINMATE:
7650                 reason = "Xboard adjudication: Stalemate";
7651                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7652                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7653                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7654                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7655                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7656                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7657                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7658                                                                         EP_CHECKMATE : EP_WINS);
7659                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7660                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7661                 }
7662                 break;
7663               case MT_CHECKMATE:
7664                 reason = "Xboard adjudication: Checkmate";
7665                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7666                 break;
7667             }
7668
7669                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7670                     case EP_STALEMATE:
7671                         result = GameIsDrawn; break;
7672                     case EP_CHECKMATE:
7673                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7674                     case EP_WINS:
7675                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7676                     default:
7677                         result = EndOfFile;
7678                 }
7679                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7680                     if(engineOpponent)
7681                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7682                     GameEnds( result, reason, GE_XBOARD );
7683                     return 1;
7684                 }
7685
7686                 /* Next absolutely insufficient mating material. */
7687                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7688                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7689                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7690
7691                      /* always flag draws, for judging claims */
7692                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7693
7694                      if(canAdjudicate && appData.materialDraws) {
7695                          /* but only adjudicate them if adjudication enabled */
7696                          if(engineOpponent) {
7697                            SendToProgram("force\n", engineOpponent); // suppress reply
7698                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7699                          }
7700                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7701                          return 1;
7702                      }
7703                 }
7704
7705                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7706                 if(gameInfo.variant == VariantXiangqi ?
7707                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7708                  : nrW + nrB == 4 &&
7709                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7710                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7711                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7712                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7713                    ) ) {
7714                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7715                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7716                           if(engineOpponent) {
7717                             SendToProgram("force\n", engineOpponent); // suppress reply
7718                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7719                           }
7720                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7721                           return 1;
7722                      }
7723                 } else moveCount = 6;
7724             }
7725
7726         // Repetition draws and 50-move rule can be applied independently of legality testing
7727
7728                 /* Check for rep-draws */
7729                 count = 0;
7730                 for(k = forwardMostMove-2;
7731                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7732                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7733                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7734                     k-=2)
7735                 {   int rights=0;
7736                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7737                         /* compare castling rights */
7738                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7739                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7740                                 rights++; /* King lost rights, while rook still had them */
7741                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7742                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7743                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7744                                    rights++; /* but at least one rook lost them */
7745                         }
7746                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7747                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7748                                 rights++;
7749                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7750                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7751                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7752                                    rights++;
7753                         }
7754                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7755                             && appData.drawRepeats > 1) {
7756                              /* adjudicate after user-specified nr of repeats */
7757                              int result = GameIsDrawn;
7758                              char *details = "XBoard adjudication: repetition draw";
7759                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7760                                 // [HGM] xiangqi: check for forbidden perpetuals
7761                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7762                                 for(m=forwardMostMove; m>k; m-=2) {
7763                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7764                                         ourPerpetual = 0; // the current mover did not always check
7765                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7766                                         hisPerpetual = 0; // the opponent did not always check
7767                                 }
7768                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7769                                                                         ourPerpetual, hisPerpetual);
7770                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7771                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7772                                     details = "Xboard adjudication: perpetual checking";
7773                                 } else
7774                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7775                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7776                                 } else
7777                                 // Now check for perpetual chases
7778                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7779                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7780                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7781                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7782                                         static char resdet[MSG_SIZ];
7783                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7784                                         details = resdet;
7785                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7786                                     } else
7787                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7788                                         break; // Abort repetition-checking loop.
7789                                 }
7790                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7791                              }
7792                              if(engineOpponent) {
7793                                SendToProgram("force\n", engineOpponent); // suppress reply
7794                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7795                              }
7796                              GameEnds( result, details, GE_XBOARD );
7797                              return 1;
7798                         }
7799                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7800                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7801                     }
7802                 }
7803
7804                 /* Now we test for 50-move draws. Determine ply count */
7805                 count = forwardMostMove;
7806                 /* look for last irreversble move */
7807                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7808                     count--;
7809                 /* if we hit starting position, add initial plies */
7810                 if( count == backwardMostMove )
7811                     count -= initialRulePlies;
7812                 count = forwardMostMove - count;
7813                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7814                         // adjust reversible move counter for checks in Xiangqi
7815                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7816                         if(i < backwardMostMove) i = backwardMostMove;
7817                         while(i <= forwardMostMove) {
7818                                 lastCheck = inCheck; // check evasion does not count
7819                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7820                                 if(inCheck || lastCheck) count--; // check does not count
7821                                 i++;
7822                         }
7823                 }
7824                 if( count >= 100)
7825                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7826                          /* this is used to judge if draw claims are legal */
7827                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7828                          if(engineOpponent) {
7829                            SendToProgram("force\n", engineOpponent); // suppress reply
7830                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7831                          }
7832                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7833                          return 1;
7834                 }
7835
7836                 /* if draw offer is pending, treat it as a draw claim
7837                  * when draw condition present, to allow engines a way to
7838                  * claim draws before making their move to avoid a race
7839                  * condition occurring after their move
7840                  */
7841                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7842                          char *p = NULL;
7843                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7844                              p = "Draw claim: 50-move rule";
7845                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7846                              p = "Draw claim: 3-fold repetition";
7847                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7848                              p = "Draw claim: insufficient mating material";
7849                          if( p != NULL && canAdjudicate) {
7850                              if(engineOpponent) {
7851                                SendToProgram("force\n", engineOpponent); // suppress reply
7852                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7853                              }
7854                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7855                              return 1;
7856                          }
7857                 }
7858
7859                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7860                     if(engineOpponent) {
7861                       SendToProgram("force\n", engineOpponent); // suppress reply
7862                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7863                     }
7864                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7865                     return 1;
7866                 }
7867         return 0;
7868 }
7869
7870 char *
7871 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7872 {   // [HGM] book: this routine intercepts moves to simulate book replies
7873     char *bookHit = NULL;
7874
7875     //first determine if the incoming move brings opponent into his book
7876     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7877         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7878     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7879     if(bookHit != NULL && !cps->bookSuspend) {
7880         // make sure opponent is not going to reply after receiving move to book position
7881         SendToProgram("force\n", cps);
7882         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7883     }
7884     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7885     // now arrange restart after book miss
7886     if(bookHit) {
7887         // after a book hit we never send 'go', and the code after the call to this routine
7888         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7889         char buf[MSG_SIZ], *move = bookHit;
7890         if(cps->useSAN) {
7891             int fromX, fromY, toX, toY;
7892             char promoChar;
7893             ChessMove moveType;
7894             move = buf + 30;
7895             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7896                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7897                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7898                                     PosFlags(forwardMostMove),
7899                                     fromY, fromX, toY, toX, promoChar, move);
7900             } else {
7901                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7902                 bookHit = NULL;
7903             }
7904         }
7905         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7906         SendToProgram(buf, cps);
7907         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7908     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7909         SendToProgram("go\n", cps);
7910         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7911     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7912         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7913             SendToProgram("go\n", cps);
7914         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7915     }
7916     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7917 }
7918
7919 int
7920 LoadError (char *errmess, ChessProgramState *cps)
7921 {   // unloads engine and switches back to -ncp mode if it was first
7922     if(cps->initDone) return FALSE;
7923     cps->isr = NULL; // this should suppress further error popups from breaking pipes
7924     DestroyChildProcess(cps->pr, 9 ); // just to be sure
7925     cps->pr = NoProc; 
7926     if(cps == &first) {
7927         appData.noChessProgram = TRUE;
7928         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
7929         gameMode = BeginningOfGame; ModeHighlight();
7930         SetNCPMode();
7931     }
7932     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
7933     DisplayMessage("", ""); // erase waiting message
7934     if(errmess) DisplayError(errmess, 0); // announce reason, if given
7935     return TRUE;
7936 }
7937
7938 char *savedMessage;
7939 ChessProgramState *savedState;
7940 void
7941 DeferredBookMove (void)
7942 {
7943         if(savedState->lastPing != savedState->lastPong)
7944                     ScheduleDelayedEvent(DeferredBookMove, 10);
7945         else
7946         HandleMachineMove(savedMessage, savedState);
7947 }
7948
7949 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7950
7951 void
7952 HandleMachineMove (char *message, ChessProgramState *cps)
7953 {
7954     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7955     char realname[MSG_SIZ];
7956     int fromX, fromY, toX, toY;
7957     ChessMove moveType;
7958     char promoChar;
7959     char *p, *pv=buf1;
7960     int machineWhite, oldError;
7961     char *bookHit;
7962
7963     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7964         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7965         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7966             DisplayError(_("Invalid pairing from pairing engine"), 0);
7967             return;
7968         }
7969         pairingReceived = 1;
7970         NextMatchGame();
7971         return; // Skim the pairing messages here.
7972     }
7973
7974     oldError = cps->userError; cps->userError = 0;
7975
7976 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7977     /*
7978      * Kludge to ignore BEL characters
7979      */
7980     while (*message == '\007') message++;
7981
7982     /*
7983      * [HGM] engine debug message: ignore lines starting with '#' character
7984      */
7985     if(cps->debug && *message == '#') return;
7986
7987     /*
7988      * Look for book output
7989      */
7990     if (cps == &first && bookRequested) {
7991         if (message[0] == '\t' || message[0] == ' ') {
7992             /* Part of the book output is here; append it */
7993             strcat(bookOutput, message);
7994             strcat(bookOutput, "  \n");
7995             return;
7996         } else if (bookOutput[0] != NULLCHAR) {
7997             /* All of book output has arrived; display it */
7998             char *p = bookOutput;
7999             while (*p != NULLCHAR) {
8000                 if (*p == '\t') *p = ' ';
8001                 p++;
8002             }
8003             DisplayInformation(bookOutput);
8004             bookRequested = FALSE;
8005             /* Fall through to parse the current output */
8006         }
8007     }
8008
8009     /*
8010      * Look for machine move.
8011      */
8012     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8013         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8014     {
8015         /* This method is only useful on engines that support ping */
8016         if (cps->lastPing != cps->lastPong) {
8017           if (gameMode == BeginningOfGame) {
8018             /* Extra move from before last new; ignore */
8019             if (appData.debugMode) {
8020                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8021             }
8022           } else {
8023             if (appData.debugMode) {
8024                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8025                         cps->which, gameMode);
8026             }
8027
8028             SendToProgram("undo\n", cps);
8029           }
8030           return;
8031         }
8032
8033         switch (gameMode) {
8034           case BeginningOfGame:
8035             /* Extra move from before last reset; ignore */
8036             if (appData.debugMode) {
8037                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8038             }
8039             return;
8040
8041           case EndOfGame:
8042           case IcsIdle:
8043           default:
8044             /* Extra move after we tried to stop.  The mode test is
8045                not a reliable way of detecting this problem, but it's
8046                the best we can do on engines that don't support ping.
8047             */
8048             if (appData.debugMode) {
8049                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8050                         cps->which, gameMode);
8051             }
8052             SendToProgram("undo\n", cps);
8053             return;
8054
8055           case MachinePlaysWhite:
8056           case IcsPlayingWhite:
8057             machineWhite = TRUE;
8058             break;
8059
8060           case MachinePlaysBlack:
8061           case IcsPlayingBlack:
8062             machineWhite = FALSE;
8063             break;
8064
8065           case TwoMachinesPlay:
8066             machineWhite = (cps->twoMachinesColor[0] == 'w');
8067             break;
8068         }
8069         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8070             if (appData.debugMode) {
8071                 fprintf(debugFP,
8072                         "Ignoring move out of turn by %s, gameMode %d"
8073                         ", forwardMost %d\n",
8074                         cps->which, gameMode, forwardMostMove);
8075             }
8076             return;
8077         }
8078
8079         if(cps->alphaRank) AlphaRank(machineMove, 4);
8080         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8081                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8082             /* Machine move could not be parsed; ignore it. */
8083           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8084                     machineMove, _(cps->which));
8085             DisplayError(buf1, 0);
8086             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8087                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8088             if (gameMode == TwoMachinesPlay) {
8089               GameEnds(machineWhite ? BlackWins : WhiteWins,
8090                        buf1, GE_XBOARD);
8091             }
8092             return;
8093         }
8094
8095         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8096         /* So we have to redo legality test with true e.p. status here,  */
8097         /* to make sure an illegal e.p. capture does not slip through,   */
8098         /* to cause a forfeit on a justified illegal-move complaint      */
8099         /* of the opponent.                                              */
8100         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8101            ChessMove moveType;
8102            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8103                              fromY, fromX, toY, toX, promoChar);
8104             if(moveType == IllegalMove) {
8105               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8106                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8107                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8108                            buf1, GE_XBOARD);
8109                 return;
8110            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8111            /* [HGM] Kludge to handle engines that send FRC-style castling
8112               when they shouldn't (like TSCP-Gothic) */
8113            switch(moveType) {
8114              case WhiteASideCastleFR:
8115              case BlackASideCastleFR:
8116                toX+=2;
8117                currentMoveString[2]++;
8118                break;
8119              case WhiteHSideCastleFR:
8120              case BlackHSideCastleFR:
8121                toX--;
8122                currentMoveString[2]--;
8123                break;
8124              default: ; // nothing to do, but suppresses warning of pedantic compilers
8125            }
8126         }
8127         hintRequested = FALSE;
8128         lastHint[0] = NULLCHAR;
8129         bookRequested = FALSE;
8130         /* Program may be pondering now */
8131         cps->maybeThinking = TRUE;
8132         if (cps->sendTime == 2) cps->sendTime = 1;
8133         if (cps->offeredDraw) cps->offeredDraw--;
8134
8135         /* [AS] Save move info*/
8136         pvInfoList[ forwardMostMove ].score = programStats.score;
8137         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8138         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8139
8140         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8141
8142         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8143         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8144             int count = 0;
8145
8146             while( count < adjudicateLossPlies ) {
8147                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8148
8149                 if( count & 1 ) {
8150                     score = -score; /* Flip score for winning side */
8151                 }
8152
8153                 if( score > adjudicateLossThreshold ) {
8154                     break;
8155                 }
8156
8157                 count++;
8158             }
8159
8160             if( count >= adjudicateLossPlies ) {
8161                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8162
8163                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8164                     "Xboard adjudication",
8165                     GE_XBOARD );
8166
8167                 return;
8168             }
8169         }
8170
8171         if(Adjudicate(cps)) {
8172             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8173             return; // [HGM] adjudicate: for all automatic game ends
8174         }
8175
8176 #if ZIPPY
8177         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8178             first.initDone) {
8179           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8180                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8181                 SendToICS("draw ");
8182                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8183           }
8184           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8185           ics_user_moved = 1;
8186           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8187                 char buf[3*MSG_SIZ];
8188
8189                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8190                         programStats.score / 100.,
8191                         programStats.depth,
8192                         programStats.time / 100.,
8193                         (unsigned int)programStats.nodes,
8194                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8195                         programStats.movelist);
8196                 SendToICS(buf);
8197 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8198           }
8199         }
8200 #endif
8201
8202         /* [AS] Clear stats for next move */
8203         ClearProgramStats();
8204         thinkOutput[0] = NULLCHAR;
8205         hiddenThinkOutputState = 0;
8206
8207         bookHit = NULL;
8208         if (gameMode == TwoMachinesPlay) {
8209             /* [HGM] relaying draw offers moved to after reception of move */
8210             /* and interpreting offer as claim if it brings draw condition */
8211             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8212                 SendToProgram("draw\n", cps->other);
8213             }
8214             if (cps->other->sendTime) {
8215                 SendTimeRemaining(cps->other,
8216                                   cps->other->twoMachinesColor[0] == 'w');
8217             }
8218             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8219             if (firstMove && !bookHit) {
8220                 firstMove = FALSE;
8221                 if (cps->other->useColors) {
8222                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8223                 }
8224                 SendToProgram("go\n", cps->other);
8225             }
8226             cps->other->maybeThinking = TRUE;
8227         }
8228
8229         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8230
8231         if (!pausing && appData.ringBellAfterMoves) {
8232             RingBell();
8233         }
8234
8235         /*
8236          * Reenable menu items that were disabled while
8237          * machine was thinking
8238          */
8239         if (gameMode != TwoMachinesPlay)
8240             SetUserThinkingEnables();
8241
8242         // [HGM] book: after book hit opponent has received move and is now in force mode
8243         // force the book reply into it, and then fake that it outputted this move by jumping
8244         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8245         if(bookHit) {
8246                 static char bookMove[MSG_SIZ]; // a bit generous?
8247
8248                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8249                 strcat(bookMove, bookHit);
8250                 message = bookMove;
8251                 cps = cps->other;
8252                 programStats.nodes = programStats.depth = programStats.time =
8253                 programStats.score = programStats.got_only_move = 0;
8254                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8255
8256                 if(cps->lastPing != cps->lastPong) {
8257                     savedMessage = message; // args for deferred call
8258                     savedState = cps;
8259                     ScheduleDelayedEvent(DeferredBookMove, 10);
8260                     return;
8261                 }
8262                 goto FakeBookMove;
8263         }
8264
8265         return;
8266     }
8267
8268     /* Set special modes for chess engines.  Later something general
8269      *  could be added here; for now there is just one kludge feature,
8270      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8271      *  when "xboard" is given as an interactive command.
8272      */
8273     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8274         cps->useSigint = FALSE;
8275         cps->useSigterm = FALSE;
8276     }
8277     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8278       ParseFeatures(message+8, cps);
8279       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8280     }
8281
8282     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8283                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8284       int dummy, s=6; char buf[MSG_SIZ];
8285       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8286       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8287       if(startedFromSetupPosition) return;
8288       ParseFEN(boards[0], &dummy, message+s);
8289       DrawPosition(TRUE, boards[0]);
8290       startedFromSetupPosition = TRUE;
8291       return;
8292     }
8293     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8294      * want this, I was asked to put it in, and obliged.
8295      */
8296     if (!strncmp(message, "setboard ", 9)) {
8297         Board initial_position;
8298
8299         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8300
8301         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8302             DisplayError(_("Bad FEN received from engine"), 0);
8303             return ;
8304         } else {
8305            Reset(TRUE, FALSE);
8306            CopyBoard(boards[0], initial_position);
8307            initialRulePlies = FENrulePlies;
8308            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8309            else gameMode = MachinePlaysBlack;
8310            DrawPosition(FALSE, boards[currentMove]);
8311         }
8312         return;
8313     }
8314
8315     /*
8316      * Look for communication commands
8317      */
8318     if (!strncmp(message, "telluser ", 9)) {
8319         if(message[9] == '\\' && message[10] == '\\')
8320             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8321         PlayTellSound();
8322         DisplayNote(message + 9);
8323         return;
8324     }
8325     if (!strncmp(message, "tellusererror ", 14)) {
8326         cps->userError = 1;
8327         if(message[14] == '\\' && message[15] == '\\')
8328             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8329         PlayTellSound();
8330         DisplayError(message + 14, 0);
8331         return;
8332     }
8333     if (!strncmp(message, "tellopponent ", 13)) {
8334       if (appData.icsActive) {
8335         if (loggedOn) {
8336           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8337           SendToICS(buf1);
8338         }
8339       } else {
8340         DisplayNote(message + 13);
8341       }
8342       return;
8343     }
8344     if (!strncmp(message, "tellothers ", 11)) {
8345       if (appData.icsActive) {
8346         if (loggedOn) {
8347           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8348           SendToICS(buf1);
8349         }
8350       }
8351       return;
8352     }
8353     if (!strncmp(message, "tellall ", 8)) {
8354       if (appData.icsActive) {
8355         if (loggedOn) {
8356           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8357           SendToICS(buf1);
8358         }
8359       } else {
8360         DisplayNote(message + 8);
8361       }
8362       return;
8363     }
8364     if (strncmp(message, "warning", 7) == 0) {
8365         /* Undocumented feature, use tellusererror in new code */
8366         DisplayError(message, 0);
8367         return;
8368     }
8369     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8370         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8371         strcat(realname, " query");
8372         AskQuestion(realname, buf2, buf1, cps->pr);
8373         return;
8374     }
8375     /* Commands from the engine directly to ICS.  We don't allow these to be
8376      *  sent until we are logged on. Crafty kibitzes have been known to
8377      *  interfere with the login process.
8378      */
8379     if (loggedOn) {
8380         if (!strncmp(message, "tellics ", 8)) {
8381             SendToICS(message + 8);
8382             SendToICS("\n");
8383             return;
8384         }
8385         if (!strncmp(message, "tellicsnoalias ", 15)) {
8386             SendToICS(ics_prefix);
8387             SendToICS(message + 15);
8388             SendToICS("\n");
8389             return;
8390         }
8391         /* The following are for backward compatibility only */
8392         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8393             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8394             SendToICS(ics_prefix);
8395             SendToICS(message);
8396             SendToICS("\n");
8397             return;
8398         }
8399     }
8400     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8401         return;
8402     }
8403     /*
8404      * If the move is illegal, cancel it and redraw the board.
8405      * Also deal with other error cases.  Matching is rather loose
8406      * here to accommodate engines written before the spec.
8407      */
8408     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8409         strncmp(message, "Error", 5) == 0) {
8410         if (StrStr(message, "name") ||
8411             StrStr(message, "rating") || StrStr(message, "?") ||
8412             StrStr(message, "result") || StrStr(message, "board") ||
8413             StrStr(message, "bk") || StrStr(message, "computer") ||
8414             StrStr(message, "variant") || StrStr(message, "hint") ||
8415             StrStr(message, "random") || StrStr(message, "depth") ||
8416             StrStr(message, "accepted")) {
8417             return;
8418         }
8419         if (StrStr(message, "protover")) {
8420           /* Program is responding to input, so it's apparently done
8421              initializing, and this error message indicates it is
8422              protocol version 1.  So we don't need to wait any longer
8423              for it to initialize and send feature commands. */
8424           FeatureDone(cps, 1);
8425           cps->protocolVersion = 1;
8426           return;
8427         }
8428         cps->maybeThinking = FALSE;
8429
8430         if (StrStr(message, "draw")) {
8431             /* Program doesn't have "draw" command */
8432             cps->sendDrawOffers = 0;
8433             return;
8434         }
8435         if (cps->sendTime != 1 &&
8436             (StrStr(message, "time") || StrStr(message, "otim"))) {
8437           /* Program apparently doesn't have "time" or "otim" command */
8438           cps->sendTime = 0;
8439           return;
8440         }
8441         if (StrStr(message, "analyze")) {
8442             cps->analysisSupport = FALSE;
8443             cps->analyzing = FALSE;
8444 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8445             EditGameEvent(); // [HGM] try to preserve loaded game
8446             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8447             DisplayError(buf2, 0);
8448             return;
8449         }
8450         if (StrStr(message, "(no matching move)st")) {
8451           /* Special kludge for GNU Chess 4 only */
8452           cps->stKludge = TRUE;
8453           SendTimeControl(cps, movesPerSession, timeControl,
8454                           timeIncrement, appData.searchDepth,
8455                           searchTime);
8456           return;
8457         }
8458         if (StrStr(message, "(no matching move)sd")) {
8459           /* Special kludge for GNU Chess 4 only */
8460           cps->sdKludge = TRUE;
8461           SendTimeControl(cps, movesPerSession, timeControl,
8462                           timeIncrement, appData.searchDepth,
8463                           searchTime);
8464           return;
8465         }
8466         if (!StrStr(message, "llegal")) {
8467             return;
8468         }
8469         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8470             gameMode == IcsIdle) return;
8471         if (forwardMostMove <= backwardMostMove) return;
8472         if (pausing) PauseEvent();
8473       if(appData.forceIllegal) {
8474             // [HGM] illegal: machine refused move; force position after move into it
8475           SendToProgram("force\n", cps);
8476           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8477                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8478                 // when black is to move, while there might be nothing on a2 or black
8479                 // might already have the move. So send the board as if white has the move.
8480                 // But first we must change the stm of the engine, as it refused the last move
8481                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8482                 if(WhiteOnMove(forwardMostMove)) {
8483                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8484                     SendBoard(cps, forwardMostMove); // kludgeless board
8485                 } else {
8486                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8487                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8488                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8489                 }
8490           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8491             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8492                  gameMode == TwoMachinesPlay)
8493               SendToProgram("go\n", cps);
8494             return;
8495       } else
8496         if (gameMode == PlayFromGameFile) {
8497             /* Stop reading this game file */
8498             gameMode = EditGame;
8499             ModeHighlight();
8500         }
8501         /* [HGM] illegal-move claim should forfeit game when Xboard */
8502         /* only passes fully legal moves                            */
8503         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8504             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8505                                 "False illegal-move claim", GE_XBOARD );
8506             return; // do not take back move we tested as valid
8507         }
8508         currentMove = forwardMostMove-1;
8509         DisplayMove(currentMove-1); /* before DisplayMoveError */
8510         SwitchClocks(forwardMostMove-1); // [HGM] race
8511         DisplayBothClocks();
8512         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8513                 parseList[currentMove], _(cps->which));
8514         DisplayMoveError(buf1);
8515         DrawPosition(FALSE, boards[currentMove]);
8516
8517         SetUserThinkingEnables();
8518         return;
8519     }
8520     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8521         /* Program has a broken "time" command that
8522            outputs a string not ending in newline.
8523            Don't use it. */
8524         cps->sendTime = 0;
8525     }
8526
8527     /*
8528      * If chess program startup fails, exit with an error message.
8529      * Attempts to recover here are futile. [HGM] Well, we try anyway
8530      */
8531     if ((StrStr(message, "unknown host") != NULL)
8532         || (StrStr(message, "No remote directory") != NULL)
8533         || (StrStr(message, "not found") != NULL)
8534         || (StrStr(message, "No such file") != NULL)
8535         || (StrStr(message, "can't alloc") != NULL)
8536         || (StrStr(message, "Permission denied") != NULL)) {
8537
8538         cps->maybeThinking = FALSE;
8539         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8540                 _(cps->which), cps->program, cps->host, message);
8541         RemoveInputSource(cps->isr);
8542         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8543             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8544             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8545         }
8546         return;
8547     }
8548
8549     /*
8550      * Look for hint output
8551      */
8552     if (sscanf(message, "Hint: %s", buf1) == 1) {
8553         if (cps == &first && hintRequested) {
8554             hintRequested = FALSE;
8555             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8556                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8557                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8558                                     PosFlags(forwardMostMove),
8559                                     fromY, fromX, toY, toX, promoChar, buf1);
8560                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8561                 DisplayInformation(buf2);
8562             } else {
8563                 /* Hint move could not be parsed!? */
8564               snprintf(buf2, sizeof(buf2),
8565                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8566                         buf1, _(cps->which));
8567                 DisplayError(buf2, 0);
8568             }
8569         } else {
8570           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8571         }
8572         return;
8573     }
8574
8575     /*
8576      * Ignore other messages if game is not in progress
8577      */
8578     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8579         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8580
8581     /*
8582      * look for win, lose, draw, or draw offer
8583      */
8584     if (strncmp(message, "1-0", 3) == 0) {
8585         char *p, *q, *r = "";
8586         p = strchr(message, '{');
8587         if (p) {
8588             q = strchr(p, '}');
8589             if (q) {
8590                 *q = NULLCHAR;
8591                 r = p + 1;
8592             }
8593         }
8594         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8595         return;
8596     } else if (strncmp(message, "0-1", 3) == 0) {
8597         char *p, *q, *r = "";
8598         p = strchr(message, '{');
8599         if (p) {
8600             q = strchr(p, '}');
8601             if (q) {
8602                 *q = NULLCHAR;
8603                 r = p + 1;
8604             }
8605         }
8606         /* Kludge for Arasan 4.1 bug */
8607         if (strcmp(r, "Black resigns") == 0) {
8608             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8609             return;
8610         }
8611         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8612         return;
8613     } else if (strncmp(message, "1/2", 3) == 0) {
8614         char *p, *q, *r = "";
8615         p = strchr(message, '{');
8616         if (p) {
8617             q = strchr(p, '}');
8618             if (q) {
8619                 *q = NULLCHAR;
8620                 r = p + 1;
8621             }
8622         }
8623
8624         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8625         return;
8626
8627     } else if (strncmp(message, "White resign", 12) == 0) {
8628         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8629         return;
8630     } else if (strncmp(message, "Black resign", 12) == 0) {
8631         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8632         return;
8633     } else if (strncmp(message, "White matches", 13) == 0 ||
8634                strncmp(message, "Black matches", 13) == 0   ) {
8635         /* [HGM] ignore GNUShogi noises */
8636         return;
8637     } else if (strncmp(message, "White", 5) == 0 &&
8638                message[5] != '(' &&
8639                StrStr(message, "Black") == NULL) {
8640         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8641         return;
8642     } else if (strncmp(message, "Black", 5) == 0 &&
8643                message[5] != '(') {
8644         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8645         return;
8646     } else if (strcmp(message, "resign") == 0 ||
8647                strcmp(message, "computer resigns") == 0) {
8648         switch (gameMode) {
8649           case MachinePlaysBlack:
8650           case IcsPlayingBlack:
8651             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8652             break;
8653           case MachinePlaysWhite:
8654           case IcsPlayingWhite:
8655             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8656             break;
8657           case TwoMachinesPlay:
8658             if (cps->twoMachinesColor[0] == 'w')
8659               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8660             else
8661               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8662             break;
8663           default:
8664             /* can't happen */
8665             break;
8666         }
8667         return;
8668     } else if (strncmp(message, "opponent mates", 14) == 0) {
8669         switch (gameMode) {
8670           case MachinePlaysBlack:
8671           case IcsPlayingBlack:
8672             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8673             break;
8674           case MachinePlaysWhite:
8675           case IcsPlayingWhite:
8676             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8677             break;
8678           case TwoMachinesPlay:
8679             if (cps->twoMachinesColor[0] == 'w')
8680               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8681             else
8682               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8683             break;
8684           default:
8685             /* can't happen */
8686             break;
8687         }
8688         return;
8689     } else if (strncmp(message, "computer mates", 14) == 0) {
8690         switch (gameMode) {
8691           case MachinePlaysBlack:
8692           case IcsPlayingBlack:
8693             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8694             break;
8695           case MachinePlaysWhite:
8696           case IcsPlayingWhite:
8697             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8698             break;
8699           case TwoMachinesPlay:
8700             if (cps->twoMachinesColor[0] == 'w')
8701               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8702             else
8703               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8704             break;
8705           default:
8706             /* can't happen */
8707             break;
8708         }
8709         return;
8710     } else if (strncmp(message, "checkmate", 9) == 0) {
8711         if (WhiteOnMove(forwardMostMove)) {
8712             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8713         } else {
8714             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8715         }
8716         return;
8717     } else if (strstr(message, "Draw") != NULL ||
8718                strstr(message, "game is a draw") != NULL) {
8719         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8720         return;
8721     } else if (strstr(message, "offer") != NULL &&
8722                strstr(message, "draw") != NULL) {
8723 #if ZIPPY
8724         if (appData.zippyPlay && first.initDone) {
8725             /* Relay offer to ICS */
8726             SendToICS(ics_prefix);
8727             SendToICS("draw\n");
8728         }
8729 #endif
8730         cps->offeredDraw = 2; /* valid until this engine moves twice */
8731         if (gameMode == TwoMachinesPlay) {
8732             if (cps->other->offeredDraw) {
8733                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8734             /* [HGM] in two-machine mode we delay relaying draw offer      */
8735             /* until after we also have move, to see if it is really claim */
8736             }
8737         } else if (gameMode == MachinePlaysWhite ||
8738                    gameMode == MachinePlaysBlack) {
8739           if (userOfferedDraw) {
8740             DisplayInformation(_("Machine accepts your draw offer"));
8741             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8742           } else {
8743             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8744           }
8745         }
8746     }
8747
8748
8749     /*
8750      * Look for thinking output
8751      */
8752     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8753           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8754                                 ) {
8755         int plylev, mvleft, mvtot, curscore, time;
8756         char mvname[MOVE_LEN];
8757         u64 nodes; // [DM]
8758         char plyext;
8759         int ignore = FALSE;
8760         int prefixHint = FALSE;
8761         mvname[0] = NULLCHAR;
8762
8763         switch (gameMode) {
8764           case MachinePlaysBlack:
8765           case IcsPlayingBlack:
8766             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8767             break;
8768           case MachinePlaysWhite:
8769           case IcsPlayingWhite:
8770             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8771             break;
8772           case AnalyzeMode:
8773           case AnalyzeFile:
8774             break;
8775           case IcsObserving: /* [DM] icsEngineAnalyze */
8776             if (!appData.icsEngineAnalyze) ignore = TRUE;
8777             break;
8778           case TwoMachinesPlay:
8779             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8780                 ignore = TRUE;
8781             }
8782             break;
8783           default:
8784             ignore = TRUE;
8785             break;
8786         }
8787
8788         if (!ignore) {
8789             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8790             buf1[0] = NULLCHAR;
8791             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8792                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8793
8794                 if (plyext != ' ' && plyext != '\t') {
8795                     time *= 100;
8796                 }
8797
8798                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8799                 if( cps->scoreIsAbsolute &&
8800                     ( gameMode == MachinePlaysBlack ||
8801                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8802                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8803                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8804                      !WhiteOnMove(currentMove)
8805                     ) )
8806                 {
8807                     curscore = -curscore;
8808                 }
8809
8810                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8811
8812                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8813                         char buf[MSG_SIZ];
8814                         FILE *f;
8815                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8816                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8817                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8818                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8819                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8820                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8821                                 fclose(f);
8822                         } else DisplayError(_("failed writing PV"), 0);
8823                 }
8824
8825                 tempStats.depth = plylev;
8826                 tempStats.nodes = nodes;
8827                 tempStats.time = time;
8828                 tempStats.score = curscore;
8829                 tempStats.got_only_move = 0;
8830
8831                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8832                         int ticklen;
8833
8834                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8835                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8836                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8837                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8838                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8839                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8840                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8841                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8842                 }
8843
8844                 /* Buffer overflow protection */
8845                 if (pv[0] != NULLCHAR) {
8846                     if (strlen(pv) >= sizeof(tempStats.movelist)
8847                         && appData.debugMode) {
8848                         fprintf(debugFP,
8849                                 "PV is too long; using the first %u bytes.\n",
8850                                 (unsigned) sizeof(tempStats.movelist) - 1);
8851                     }
8852
8853                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8854                 } else {
8855                     sprintf(tempStats.movelist, " no PV\n");
8856                 }
8857
8858                 if (tempStats.seen_stat) {
8859                     tempStats.ok_to_send = 1;
8860                 }
8861
8862                 if (strchr(tempStats.movelist, '(') != NULL) {
8863                     tempStats.line_is_book = 1;
8864                     tempStats.nr_moves = 0;
8865                     tempStats.moves_left = 0;
8866                 } else {
8867                     tempStats.line_is_book = 0;
8868                 }
8869
8870                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8871                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8872
8873                 SendProgramStatsToFrontend( cps, &tempStats );
8874
8875                 /*
8876                     [AS] Protect the thinkOutput buffer from overflow... this
8877                     is only useful if buf1 hasn't overflowed first!
8878                 */
8879                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8880                          plylev,
8881                          (gameMode == TwoMachinesPlay ?
8882                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8883                          ((double) curscore) / 100.0,
8884                          prefixHint ? lastHint : "",
8885                          prefixHint ? " " : "" );
8886
8887                 if( buf1[0] != NULLCHAR ) {
8888                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8889
8890                     if( strlen(pv) > max_len ) {
8891                         if( appData.debugMode) {
8892                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8893                         }
8894                         pv[max_len+1] = '\0';
8895                     }
8896
8897                     strcat( thinkOutput, pv);
8898                 }
8899
8900                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8901                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8902                     DisplayMove(currentMove - 1);
8903                 }
8904                 return;
8905
8906             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8907                 /* crafty (9.25+) says "(only move) <move>"
8908                  * if there is only 1 legal move
8909                  */
8910                 sscanf(p, "(only move) %s", buf1);
8911                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8912                 sprintf(programStats.movelist, "%s (only move)", buf1);
8913                 programStats.depth = 1;
8914                 programStats.nr_moves = 1;
8915                 programStats.moves_left = 1;
8916                 programStats.nodes = 1;
8917                 programStats.time = 1;
8918                 programStats.got_only_move = 1;
8919
8920                 /* Not really, but we also use this member to
8921                    mean "line isn't going to change" (Crafty
8922                    isn't searching, so stats won't change) */
8923                 programStats.line_is_book = 1;
8924
8925                 SendProgramStatsToFrontend( cps, &programStats );
8926
8927                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8928                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8929                     DisplayMove(currentMove - 1);
8930                 }
8931                 return;
8932             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8933                               &time, &nodes, &plylev, &mvleft,
8934                               &mvtot, mvname) >= 5) {
8935                 /* The stat01: line is from Crafty (9.29+) in response
8936                    to the "." command */
8937                 programStats.seen_stat = 1;
8938                 cps->maybeThinking = TRUE;
8939
8940                 if (programStats.got_only_move || !appData.periodicUpdates)
8941                   return;
8942
8943                 programStats.depth = plylev;
8944                 programStats.time = time;
8945                 programStats.nodes = nodes;
8946                 programStats.moves_left = mvleft;
8947                 programStats.nr_moves = mvtot;
8948                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8949                 programStats.ok_to_send = 1;
8950                 programStats.movelist[0] = '\0';
8951
8952                 SendProgramStatsToFrontend( cps, &programStats );
8953
8954                 return;
8955
8956             } else if (strncmp(message,"++",2) == 0) {
8957                 /* Crafty 9.29+ outputs this */
8958                 programStats.got_fail = 2;
8959                 return;
8960
8961             } else if (strncmp(message,"--",2) == 0) {
8962                 /* Crafty 9.29+ outputs this */
8963                 programStats.got_fail = 1;
8964                 return;
8965
8966             } else if (thinkOutput[0] != NULLCHAR &&
8967                        strncmp(message, "    ", 4) == 0) {
8968                 unsigned message_len;
8969
8970                 p = message;
8971                 while (*p && *p == ' ') p++;
8972
8973                 message_len = strlen( p );
8974
8975                 /* [AS] Avoid buffer overflow */
8976                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8977                     strcat(thinkOutput, " ");
8978                     strcat(thinkOutput, p);
8979                 }
8980
8981                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8982                     strcat(programStats.movelist, " ");
8983                     strcat(programStats.movelist, p);
8984                 }
8985
8986                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8987                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8988                     DisplayMove(currentMove - 1);
8989                 }
8990                 return;
8991             }
8992         }
8993         else {
8994             buf1[0] = NULLCHAR;
8995
8996             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8997                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8998             {
8999                 ChessProgramStats cpstats;
9000
9001                 if (plyext != ' ' && plyext != '\t') {
9002                     time *= 100;
9003                 }
9004
9005                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9006                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9007                     curscore = -curscore;
9008                 }
9009
9010                 cpstats.depth = plylev;
9011                 cpstats.nodes = nodes;
9012                 cpstats.time = time;
9013                 cpstats.score = curscore;
9014                 cpstats.got_only_move = 0;
9015                 cpstats.movelist[0] = '\0';
9016
9017                 if (buf1[0] != NULLCHAR) {
9018                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9019                 }
9020
9021                 cpstats.ok_to_send = 0;
9022                 cpstats.line_is_book = 0;
9023                 cpstats.nr_moves = 0;
9024                 cpstats.moves_left = 0;
9025
9026                 SendProgramStatsToFrontend( cps, &cpstats );
9027             }
9028         }
9029     }
9030 }
9031
9032
9033 /* Parse a game score from the character string "game", and
9034    record it as the history of the current game.  The game
9035    score is NOT assumed to start from the standard position.
9036    The display is not updated in any way.
9037    */
9038 void
9039 ParseGameHistory (char *game)
9040 {
9041     ChessMove moveType;
9042     int fromX, fromY, toX, toY, boardIndex;
9043     char promoChar;
9044     char *p, *q;
9045     char buf[MSG_SIZ];
9046
9047     if (appData.debugMode)
9048       fprintf(debugFP, "Parsing game history: %s\n", game);
9049
9050     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9051     gameInfo.site = StrSave(appData.icsHost);
9052     gameInfo.date = PGNDate();
9053     gameInfo.round = StrSave("-");
9054
9055     /* Parse out names of players */
9056     while (*game == ' ') game++;
9057     p = buf;
9058     while (*game != ' ') *p++ = *game++;
9059     *p = NULLCHAR;
9060     gameInfo.white = StrSave(buf);
9061     while (*game == ' ') game++;
9062     p = buf;
9063     while (*game != ' ' && *game != '\n') *p++ = *game++;
9064     *p = NULLCHAR;
9065     gameInfo.black = StrSave(buf);
9066
9067     /* Parse moves */
9068     boardIndex = blackPlaysFirst ? 1 : 0;
9069     yynewstr(game);
9070     for (;;) {
9071         yyboardindex = boardIndex;
9072         moveType = (ChessMove) Myylex();
9073         switch (moveType) {
9074           case IllegalMove:             /* maybe suicide chess, etc. */
9075   if (appData.debugMode) {
9076     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9077     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9078     setbuf(debugFP, NULL);
9079   }
9080           case WhitePromotion:
9081           case BlackPromotion:
9082           case WhiteNonPromotion:
9083           case BlackNonPromotion:
9084           case NormalMove:
9085           case WhiteCapturesEnPassant:
9086           case BlackCapturesEnPassant:
9087           case WhiteKingSideCastle:
9088           case WhiteQueenSideCastle:
9089           case BlackKingSideCastle:
9090           case BlackQueenSideCastle:
9091           case WhiteKingSideCastleWild:
9092           case WhiteQueenSideCastleWild:
9093           case BlackKingSideCastleWild:
9094           case BlackQueenSideCastleWild:
9095           /* PUSH Fabien */
9096           case WhiteHSideCastleFR:
9097           case WhiteASideCastleFR:
9098           case BlackHSideCastleFR:
9099           case BlackASideCastleFR:
9100           /* POP Fabien */
9101             fromX = currentMoveString[0] - AAA;
9102             fromY = currentMoveString[1] - ONE;
9103             toX = currentMoveString[2] - AAA;
9104             toY = currentMoveString[3] - ONE;
9105             promoChar = currentMoveString[4];
9106             break;
9107           case WhiteDrop:
9108           case BlackDrop:
9109             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9110             fromX = moveType == WhiteDrop ?
9111               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9112             (int) CharToPiece(ToLower(currentMoveString[0]));
9113             fromY = DROP_RANK;
9114             toX = currentMoveString[2] - AAA;
9115             toY = currentMoveString[3] - ONE;
9116             promoChar = NULLCHAR;
9117             break;
9118           case AmbiguousMove:
9119             /* bug? */
9120             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9121   if (appData.debugMode) {
9122     fprintf(debugFP, "Ambiguous 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 ImpossibleMove:
9129             /* bug? */
9130             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9131   if (appData.debugMode) {
9132     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9133     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9134     setbuf(debugFP, NULL);
9135   }
9136             DisplayError(buf, 0);
9137             return;
9138           case EndOfFile:
9139             if (boardIndex < backwardMostMove) {
9140                 /* Oops, gap.  How did that happen? */
9141                 DisplayError(_("Gap in move list"), 0);
9142                 return;
9143             }
9144             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9145             if (boardIndex > forwardMostMove) {
9146                 forwardMostMove = boardIndex;
9147             }
9148             return;
9149           case ElapsedTime:
9150             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9151                 strcat(parseList[boardIndex-1], " ");
9152                 strcat(parseList[boardIndex-1], yy_text);
9153             }
9154             continue;
9155           case Comment:
9156           case PGNTag:
9157           case NAG:
9158           default:
9159             /* ignore */
9160             continue;
9161           case WhiteWins:
9162           case BlackWins:
9163           case GameIsDrawn:
9164           case GameUnfinished:
9165             if (gameMode == IcsExamining) {
9166                 if (boardIndex < backwardMostMove) {
9167                     /* Oops, gap.  How did that happen? */
9168                     return;
9169                 }
9170                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9171                 return;
9172             }
9173             gameInfo.result = moveType;
9174             p = strchr(yy_text, '{');
9175             if (p == NULL) p = strchr(yy_text, '(');
9176             if (p == NULL) {
9177                 p = yy_text;
9178                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9179             } else {
9180                 q = strchr(p, *p == '{' ? '}' : ')');
9181                 if (q != NULL) *q = NULLCHAR;
9182                 p++;
9183             }
9184             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9185             gameInfo.resultDetails = StrSave(p);
9186             continue;
9187         }
9188         if (boardIndex >= forwardMostMove &&
9189             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9190             backwardMostMove = blackPlaysFirst ? 1 : 0;
9191             return;
9192         }
9193         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9194                                  fromY, fromX, toY, toX, promoChar,
9195                                  parseList[boardIndex]);
9196         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9197         /* currentMoveString is set as a side-effect of yylex */
9198         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9199         strcat(moveList[boardIndex], "\n");
9200         boardIndex++;
9201         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9202         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9203           case MT_NONE:
9204           case MT_STALEMATE:
9205           default:
9206             break;
9207           case MT_CHECK:
9208             if(gameInfo.variant != VariantShogi)
9209                 strcat(parseList[boardIndex - 1], "+");
9210             break;
9211           case MT_CHECKMATE:
9212           case MT_STAINMATE:
9213             strcat(parseList[boardIndex - 1], "#");
9214             break;
9215         }
9216     }
9217 }
9218
9219
9220 /* Apply a move to the given board  */
9221 void
9222 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9223 {
9224   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9225   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9226
9227     /* [HGM] compute & store e.p. status and castling rights for new position */
9228     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9229
9230       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9231       oldEP = (signed char)board[EP_STATUS];
9232       board[EP_STATUS] = EP_NONE;
9233
9234   if (fromY == DROP_RANK) {
9235         /* must be first */
9236         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9237             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9238             return;
9239         }
9240         piece = board[toY][toX] = (ChessSquare) fromX;
9241   } else {
9242       int i;
9243
9244       if( board[toY][toX] != EmptySquare )
9245            board[EP_STATUS] = EP_CAPTURE;
9246
9247       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9248            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9249                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9250       } else
9251       if( board[fromY][fromX] == WhitePawn ) {
9252            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9253                board[EP_STATUS] = EP_PAWN_MOVE;
9254            if( toY-fromY==2) {
9255                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9256                         gameInfo.variant != VariantBerolina || toX < fromX)
9257                       board[EP_STATUS] = toX | berolina;
9258                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9259                         gameInfo.variant != VariantBerolina || toX > fromX)
9260                       board[EP_STATUS] = toX;
9261            }
9262       } else
9263       if( board[fromY][fromX] == BlackPawn ) {
9264            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9265                board[EP_STATUS] = EP_PAWN_MOVE;
9266            if( toY-fromY== -2) {
9267                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9268                         gameInfo.variant != VariantBerolina || toX < fromX)
9269                       board[EP_STATUS] = toX | berolina;
9270                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9271                         gameInfo.variant != VariantBerolina || toX > fromX)
9272                       board[EP_STATUS] = toX;
9273            }
9274        }
9275
9276        for(i=0; i<nrCastlingRights; i++) {
9277            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9278               board[CASTLING][i] == toX   && castlingRank[i] == toY
9279              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9280        }
9281
9282      if (fromX == toX && fromY == toY) return;
9283
9284      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9285      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9286      if(gameInfo.variant == VariantKnightmate)
9287          king += (int) WhiteUnicorn - (int) WhiteKing;
9288
9289     /* Code added by Tord: */
9290     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9291     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9292         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9293       board[fromY][fromX] = EmptySquare;
9294       board[toY][toX] = EmptySquare;
9295       if((toX > fromX) != (piece == WhiteRook)) {
9296         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9297       } else {
9298         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9299       }
9300     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9301                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9302       board[fromY][fromX] = EmptySquare;
9303       board[toY][toX] = EmptySquare;
9304       if((toX > fromX) != (piece == BlackRook)) {
9305         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9306       } else {
9307         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9308       }
9309     /* End of code added by Tord */
9310
9311     } else if (board[fromY][fromX] == king
9312         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9313         && toY == fromY && toX > fromX+1) {
9314         board[fromY][fromX] = EmptySquare;
9315         board[toY][toX] = king;
9316         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9317         board[fromY][BOARD_RGHT-1] = EmptySquare;
9318     } else if (board[fromY][fromX] == king
9319         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9320                && toY == fromY && toX < fromX-1) {
9321         board[fromY][fromX] = EmptySquare;
9322         board[toY][toX] = king;
9323         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9324         board[fromY][BOARD_LEFT] = EmptySquare;
9325     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9326                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9327                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9328                ) {
9329         /* white pawn promotion */
9330         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9331         if(gameInfo.variant==VariantBughouse ||
9332            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9333             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9334         board[fromY][fromX] = EmptySquare;
9335     } else if ((fromY >= BOARD_HEIGHT>>1)
9336                && (toX != fromX)
9337                && gameInfo.variant != VariantXiangqi
9338                && gameInfo.variant != VariantBerolina
9339                && (board[fromY][fromX] == WhitePawn)
9340                && (board[toY][toX] == EmptySquare)) {
9341         board[fromY][fromX] = EmptySquare;
9342         board[toY][toX] = WhitePawn;
9343         captured = board[toY - 1][toX];
9344         board[toY - 1][toX] = EmptySquare;
9345     } else if ((fromY == BOARD_HEIGHT-4)
9346                && (toX == fromX)
9347                && gameInfo.variant == VariantBerolina
9348                && (board[fromY][fromX] == WhitePawn)
9349                && (board[toY][toX] == EmptySquare)) {
9350         board[fromY][fromX] = EmptySquare;
9351         board[toY][toX] = WhitePawn;
9352         if(oldEP & EP_BEROLIN_A) {
9353                 captured = board[fromY][fromX-1];
9354                 board[fromY][fromX-1] = EmptySquare;
9355         }else{  captured = board[fromY][fromX+1];
9356                 board[fromY][fromX+1] = EmptySquare;
9357         }
9358     } else if (board[fromY][fromX] == king
9359         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9360                && toY == fromY && toX > fromX+1) {
9361         board[fromY][fromX] = EmptySquare;
9362         board[toY][toX] = king;
9363         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9364         board[fromY][BOARD_RGHT-1] = EmptySquare;
9365     } else if (board[fromY][fromX] == king
9366         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9367                && toY == fromY && toX < fromX-1) {
9368         board[fromY][fromX] = EmptySquare;
9369         board[toY][toX] = king;
9370         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9371         board[fromY][BOARD_LEFT] = EmptySquare;
9372     } else if (fromY == 7 && fromX == 3
9373                && board[fromY][fromX] == BlackKing
9374                && toY == 7 && toX == 5) {
9375         board[fromY][fromX] = EmptySquare;
9376         board[toY][toX] = BlackKing;
9377         board[fromY][7] = EmptySquare;
9378         board[toY][4] = BlackRook;
9379     } else if (fromY == 7 && fromX == 3
9380                && board[fromY][fromX] == BlackKing
9381                && toY == 7 && toX == 1) {
9382         board[fromY][fromX] = EmptySquare;
9383         board[toY][toX] = BlackKing;
9384         board[fromY][0] = EmptySquare;
9385         board[toY][2] = BlackRook;
9386     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9387                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9388                && toY < promoRank && promoChar
9389                ) {
9390         /* black pawn promotion */
9391         board[toY][toX] = CharToPiece(ToLower(promoChar));
9392         if(gameInfo.variant==VariantBughouse ||
9393            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9394             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9395         board[fromY][fromX] = EmptySquare;
9396     } else if ((fromY < BOARD_HEIGHT>>1)
9397                && (toX != fromX)
9398                && gameInfo.variant != VariantXiangqi
9399                && gameInfo.variant != VariantBerolina
9400                && (board[fromY][fromX] == BlackPawn)
9401                && (board[toY][toX] == EmptySquare)) {
9402         board[fromY][fromX] = EmptySquare;
9403         board[toY][toX] = BlackPawn;
9404         captured = board[toY + 1][toX];
9405         board[toY + 1][toX] = EmptySquare;
9406     } else if ((fromY == 3)
9407                && (toX == fromX)
9408                && gameInfo.variant == VariantBerolina
9409                && (board[fromY][fromX] == BlackPawn)
9410                && (board[toY][toX] == EmptySquare)) {
9411         board[fromY][fromX] = EmptySquare;
9412         board[toY][toX] = BlackPawn;
9413         if(oldEP & EP_BEROLIN_A) {
9414                 captured = board[fromY][fromX-1];
9415                 board[fromY][fromX-1] = EmptySquare;
9416         }else{  captured = board[fromY][fromX+1];
9417                 board[fromY][fromX+1] = EmptySquare;
9418         }
9419     } else {
9420         board[toY][toX] = board[fromY][fromX];
9421         board[fromY][fromX] = EmptySquare;
9422     }
9423   }
9424
9425     if (gameInfo.holdingsWidth != 0) {
9426
9427       /* !!A lot more code needs to be written to support holdings  */
9428       /* [HGM] OK, so I have written it. Holdings are stored in the */
9429       /* penultimate board files, so they are automaticlly stored   */
9430       /* in the game history.                                       */
9431       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9432                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9433         /* Delete from holdings, by decreasing count */
9434         /* and erasing image if necessary            */
9435         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9436         if(p < (int) BlackPawn) { /* white drop */
9437              p -= (int)WhitePawn;
9438                  p = PieceToNumber((ChessSquare)p);
9439              if(p >= gameInfo.holdingsSize) p = 0;
9440              if(--board[p][BOARD_WIDTH-2] <= 0)
9441                   board[p][BOARD_WIDTH-1] = EmptySquare;
9442              if((int)board[p][BOARD_WIDTH-2] < 0)
9443                         board[p][BOARD_WIDTH-2] = 0;
9444         } else {                  /* black drop */
9445              p -= (int)BlackPawn;
9446                  p = PieceToNumber((ChessSquare)p);
9447              if(p >= gameInfo.holdingsSize) p = 0;
9448              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9449                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9450              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9451                         board[BOARD_HEIGHT-1-p][1] = 0;
9452         }
9453       }
9454       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9455           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9456         /* [HGM] holdings: Add to holdings, if holdings exist */
9457         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9458                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9459                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9460         }
9461         p = (int) captured;
9462         if (p >= (int) BlackPawn) {
9463           p -= (int)BlackPawn;
9464           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9465                   /* in Shogi restore piece to its original  first */
9466                   captured = (ChessSquare) (DEMOTED captured);
9467                   p = DEMOTED p;
9468           }
9469           p = PieceToNumber((ChessSquare)p);
9470           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9471           board[p][BOARD_WIDTH-2]++;
9472           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9473         } else {
9474           p -= (int)WhitePawn;
9475           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9476                   captured = (ChessSquare) (DEMOTED captured);
9477                   p = DEMOTED p;
9478           }
9479           p = PieceToNumber((ChessSquare)p);
9480           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9481           board[BOARD_HEIGHT-1-p][1]++;
9482           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9483         }
9484       }
9485     } else if (gameInfo.variant == VariantAtomic) {
9486       if (captured != EmptySquare) {
9487         int y, x;
9488         for (y = toY-1; y <= toY+1; y++) {
9489           for (x = toX-1; x <= toX+1; x++) {
9490             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9491                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9492               board[y][x] = EmptySquare;
9493             }
9494           }
9495         }
9496         board[toY][toX] = EmptySquare;
9497       }
9498     }
9499     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9500         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9501     } else
9502     if(promoChar == '+') {
9503         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9504         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9505     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9506         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9507         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9508            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9509         board[toY][toX] = newPiece;
9510     }
9511     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9512                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9513         // [HGM] superchess: take promotion piece out of holdings
9514         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9515         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9516             if(!--board[k][BOARD_WIDTH-2])
9517                 board[k][BOARD_WIDTH-1] = EmptySquare;
9518         } else {
9519             if(!--board[BOARD_HEIGHT-1-k][1])
9520                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9521         }
9522     }
9523
9524 }
9525
9526 /* Updates forwardMostMove */
9527 void
9528 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9529 {
9530 //    forwardMostMove++; // [HGM] bare: moved downstream
9531
9532     (void) CoordsToAlgebraic(boards[forwardMostMove],
9533                              PosFlags(forwardMostMove),
9534                              fromY, fromX, toY, toX, promoChar,
9535                              parseList[forwardMostMove]);
9536
9537     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9538         int timeLeft; static int lastLoadFlag=0; int king, piece;
9539         piece = boards[forwardMostMove][fromY][fromX];
9540         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9541         if(gameInfo.variant == VariantKnightmate)
9542             king += (int) WhiteUnicorn - (int) WhiteKing;
9543         if(forwardMostMove == 0) {
9544             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9545                 fprintf(serverMoves, "%s;", UserName());
9546             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9547                 fprintf(serverMoves, "%s;", second.tidy);
9548             fprintf(serverMoves, "%s;", first.tidy);
9549             if(gameMode == MachinePlaysWhite)
9550                 fprintf(serverMoves, "%s;", UserName());
9551             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9552                 fprintf(serverMoves, "%s;", second.tidy);
9553         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9554         lastLoadFlag = loadFlag;
9555         // print base move
9556         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9557         // print castling suffix
9558         if( toY == fromY && piece == king ) {
9559             if(toX-fromX > 1)
9560                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9561             if(fromX-toX >1)
9562                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9563         }
9564         // e.p. suffix
9565         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9566              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9567              boards[forwardMostMove][toY][toX] == EmptySquare
9568              && fromX != toX && fromY != toY)
9569                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9570         // promotion suffix
9571         if(promoChar != NULLCHAR)
9572                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9573         if(!loadFlag) {
9574                 char buf[MOVE_LEN*2], *p; int len;
9575             fprintf(serverMoves, "/%d/%d",
9576                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9577             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9578             else                      timeLeft = blackTimeRemaining/1000;
9579             fprintf(serverMoves, "/%d", timeLeft);
9580                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9581                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9582                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9583             fprintf(serverMoves, "/%s", buf);
9584         }
9585         fflush(serverMoves);
9586     }
9587
9588     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9589         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9590       return;
9591     }
9592     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9593     if (commentList[forwardMostMove+1] != NULL) {
9594         free(commentList[forwardMostMove+1]);
9595         commentList[forwardMostMove+1] = NULL;
9596     }
9597     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9598     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9599     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9600     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9601     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9602     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9603     adjustedClock = FALSE;
9604     gameInfo.result = GameUnfinished;
9605     if (gameInfo.resultDetails != NULL) {
9606         free(gameInfo.resultDetails);
9607         gameInfo.resultDetails = NULL;
9608     }
9609     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9610                               moveList[forwardMostMove - 1]);
9611     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9612       case MT_NONE:
9613       case MT_STALEMATE:
9614       default:
9615         break;
9616       case MT_CHECK:
9617         if(gameInfo.variant != VariantShogi)
9618             strcat(parseList[forwardMostMove - 1], "+");
9619         break;
9620       case MT_CHECKMATE:
9621       case MT_STAINMATE:
9622         strcat(parseList[forwardMostMove - 1], "#");
9623         break;
9624     }
9625
9626 }
9627
9628 /* Updates currentMove if not pausing */
9629 void
9630 ShowMove (int fromX, int fromY, int toX, int toY)
9631 {
9632     int instant = (gameMode == PlayFromGameFile) ?
9633         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9634     if(appData.noGUI) return;
9635     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9636         if (!instant) {
9637             if (forwardMostMove == currentMove + 1) {
9638                 AnimateMove(boards[forwardMostMove - 1],
9639                             fromX, fromY, toX, toY);
9640             }
9641             if (appData.highlightLastMove) {
9642                 SetHighlights(fromX, fromY, toX, toY);
9643             }
9644         }
9645         currentMove = forwardMostMove;
9646     }
9647
9648     if (instant) return;
9649
9650     DisplayMove(currentMove - 1);
9651     DrawPosition(FALSE, boards[currentMove]);
9652     DisplayBothClocks();
9653     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9654 }
9655
9656 void
9657 SendEgtPath (ChessProgramState *cps)
9658 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9659         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9660
9661         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9662
9663         while(*p) {
9664             char c, *q = name+1, *r, *s;
9665
9666             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9667             while(*p && *p != ',') *q++ = *p++;
9668             *q++ = ':'; *q = 0;
9669             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9670                 strcmp(name, ",nalimov:") == 0 ) {
9671                 // take nalimov path from the menu-changeable option first, if it is defined
9672               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9673                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9674             } else
9675             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9676                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9677                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9678                 s = r = StrStr(s, ":") + 1; // beginning of path info
9679                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9680                 c = *r; *r = 0;             // temporarily null-terminate path info
9681                     *--q = 0;               // strip of trailig ':' from name
9682                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9683                 *r = c;
9684                 SendToProgram(buf,cps);     // send egtbpath command for this format
9685             }
9686             if(*p == ',') p++; // read away comma to position for next format name
9687         }
9688 }
9689
9690 void
9691 InitChessProgram (ChessProgramState *cps, int setup)
9692 /* setup needed to setup FRC opening position */
9693 {
9694     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9695     if (appData.noChessProgram) return;
9696     hintRequested = FALSE;
9697     bookRequested = FALSE;
9698
9699     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9700     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9701     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9702     if(cps->memSize) { /* [HGM] memory */
9703       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9704         SendToProgram(buf, cps);
9705     }
9706     SendEgtPath(cps); /* [HGM] EGT */
9707     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9708       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9709         SendToProgram(buf, cps);
9710     }
9711
9712     SendToProgram(cps->initString, cps);
9713     if (gameInfo.variant != VariantNormal &&
9714         gameInfo.variant != VariantLoadable
9715         /* [HGM] also send variant if board size non-standard */
9716         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9717                                             ) {
9718       char *v = VariantName(gameInfo.variant);
9719       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9720         /* [HGM] in protocol 1 we have to assume all variants valid */
9721         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9722         DisplayFatalError(buf, 0, 1);
9723         return;
9724       }
9725
9726       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9727       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9728       if( gameInfo.variant == VariantXiangqi )
9729            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9730       if( gameInfo.variant == VariantShogi )
9731            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9732       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9733            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9734       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9735           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9736            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9737       if( gameInfo.variant == VariantCourier )
9738            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9739       if( gameInfo.variant == VariantSuper )
9740            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9741       if( gameInfo.variant == VariantGreat )
9742            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9743       if( gameInfo.variant == VariantSChess )
9744            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9745       if( gameInfo.variant == VariantGrand )
9746            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9747
9748       if(overruled) {
9749         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9750                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9751            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9752            if(StrStr(cps->variants, b) == NULL) {
9753                // specific sized variant not known, check if general sizing allowed
9754                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9755                    if(StrStr(cps->variants, "boardsize") == NULL) {
9756                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9757                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9758                        DisplayFatalError(buf, 0, 1);
9759                        return;
9760                    }
9761                    /* [HGM] here we really should compare with the maximum supported board size */
9762                }
9763            }
9764       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9765       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9766       SendToProgram(buf, cps);
9767     }
9768     currentlyInitializedVariant = gameInfo.variant;
9769
9770     /* [HGM] send opening position in FRC to first engine */
9771     if(setup) {
9772           SendToProgram("force\n", cps);
9773           SendBoard(cps, 0);
9774           /* engine is now in force mode! Set flag to wake it up after first move. */
9775           setboardSpoiledMachineBlack = 1;
9776     }
9777
9778     if (cps->sendICS) {
9779       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9780       SendToProgram(buf, cps);
9781     }
9782     cps->maybeThinking = FALSE;
9783     cps->offeredDraw = 0;
9784     if (!appData.icsActive) {
9785         SendTimeControl(cps, movesPerSession, timeControl,
9786                         timeIncrement, appData.searchDepth,
9787                         searchTime);
9788     }
9789     if (appData.showThinking
9790         // [HGM] thinking: four options require thinking output to be sent
9791         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9792                                 ) {
9793         SendToProgram("post\n", cps);
9794     }
9795     SendToProgram("hard\n", cps);
9796     if (!appData.ponderNextMove) {
9797         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9798            it without being sure what state we are in first.  "hard"
9799            is not a toggle, so that one is OK.
9800          */
9801         SendToProgram("easy\n", cps);
9802     }
9803     if (cps->usePing) {
9804       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9805       SendToProgram(buf, cps);
9806     }
9807     cps->initDone = TRUE;
9808     ClearEngineOutputPane(cps == &second);
9809 }
9810
9811
9812 void
9813 StartChessProgram (ChessProgramState *cps)
9814 {
9815     char buf[MSG_SIZ];
9816     int err;
9817
9818     if (appData.noChessProgram) return;
9819     cps->initDone = FALSE;
9820
9821     if (strcmp(cps->host, "localhost") == 0) {
9822         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9823     } else if (*appData.remoteShell == NULLCHAR) {
9824         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9825     } else {
9826         if (*appData.remoteUser == NULLCHAR) {
9827           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9828                     cps->program);
9829         } else {
9830           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9831                     cps->host, appData.remoteUser, cps->program);
9832         }
9833         err = StartChildProcess(buf, "", &cps->pr);
9834     }
9835
9836     if (err != 0) {
9837       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9838         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9839         if(cps != &first) return;
9840         appData.noChessProgram = TRUE;
9841         ThawUI();
9842         SetNCPMode();
9843 //      DisplayFatalError(buf, err, 1);
9844 //      cps->pr = NoProc;
9845 //      cps->isr = NULL;
9846         return;
9847     }
9848
9849     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9850     if (cps->protocolVersion > 1) {
9851       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9852       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9853       cps->comboCnt = 0;  //                and values of combo boxes
9854       SendToProgram(buf, cps);
9855     } else {
9856       SendToProgram("xboard\n", cps);
9857     }
9858 }
9859
9860 void
9861 TwoMachinesEventIfReady P((void))
9862 {
9863   static int curMess = 0;
9864   if (first.lastPing != first.lastPong) {
9865     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9866     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9867     return;
9868   }
9869   if (second.lastPing != second.lastPong) {
9870     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9871     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9872     return;
9873   }
9874   DisplayMessage("", ""); curMess = 0;
9875   ThawUI();
9876   TwoMachinesEvent();
9877 }
9878
9879 char *
9880 MakeName (char *template)
9881 {
9882     time_t clock;
9883     struct tm *tm;
9884     static char buf[MSG_SIZ];
9885     char *p = buf;
9886     int i;
9887
9888     clock = time((time_t *)NULL);
9889     tm = localtime(&clock);
9890
9891     while(*p++ = *template++) if(p[-1] == '%') {
9892         switch(*template++) {
9893           case 0:   *p = 0; return buf;
9894           case 'Y': i = tm->tm_year+1900; break;
9895           case 'y': i = tm->tm_year-100; break;
9896           case 'M': i = tm->tm_mon+1; break;
9897           case 'd': i = tm->tm_mday; break;
9898           case 'h': i = tm->tm_hour; break;
9899           case 'm': i = tm->tm_min; break;
9900           case 's': i = tm->tm_sec; break;
9901           default:  i = 0;
9902         }
9903         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9904     }
9905     return buf;
9906 }
9907
9908 int
9909 CountPlayers (char *p)
9910 {
9911     int n = 0;
9912     while(p = strchr(p, '\n')) p++, n++; // count participants
9913     return n;
9914 }
9915
9916 FILE *
9917 WriteTourneyFile (char *results, FILE *f)
9918 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9919     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9920     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9921         // create a file with tournament description
9922         fprintf(f, "-participants {%s}\n", appData.participants);
9923         fprintf(f, "-seedBase %d\n", appData.seedBase);
9924         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9925         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9926         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9927         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9928         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9929         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9930         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9931         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9932         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9933         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9934         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9935         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9936         if(searchTime > 0)
9937                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9938         else {
9939                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9940                 fprintf(f, "-tc %s\n", appData.timeControl);
9941                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9942         }
9943         fprintf(f, "-results \"%s\"\n", results);
9944     }
9945     return f;
9946 }
9947
9948 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9949
9950 void
9951 Substitute (char *participants, int expunge)
9952 {
9953     int i, changed, changes=0, nPlayers=0;
9954     char *p, *q, *r, buf[MSG_SIZ];
9955     if(participants == NULL) return;
9956     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9957     r = p = participants; q = appData.participants;
9958     while(*p && *p == *q) {
9959         if(*p == '\n') r = p+1, nPlayers++;
9960         p++; q++;
9961     }
9962     if(*p) { // difference
9963         while(*p && *p++ != '\n');
9964         while(*q && *q++ != '\n');
9965       changed = nPlayers;
9966         changes = 1 + (strcmp(p, q) != 0);
9967     }
9968     if(changes == 1) { // a single engine mnemonic was changed
9969         q = r; while(*q) nPlayers += (*q++ == '\n');
9970         p = buf; while(*r && (*p = *r++) != '\n') p++;
9971         *p = NULLCHAR;
9972         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9973         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9974         if(mnemonic[i]) { // The substitute is valid
9975             FILE *f;
9976             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9977                 flock(fileno(f), LOCK_EX);
9978                 ParseArgsFromFile(f);
9979                 fseek(f, 0, SEEK_SET);
9980                 FREE(appData.participants); appData.participants = participants;
9981                 if(expunge) { // erase results of replaced engine
9982                     int len = strlen(appData.results), w, b, dummy;
9983                     for(i=0; i<len; i++) {
9984                         Pairing(i, nPlayers, &w, &b, &dummy);
9985                         if((w == changed || b == changed) && appData.results[i] == '*') {
9986                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9987                             fclose(f);
9988                             return;
9989                         }
9990                     }
9991                     for(i=0; i<len; i++) {
9992                         Pairing(i, nPlayers, &w, &b, &dummy);
9993                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9994                     }
9995                 }
9996                 WriteTourneyFile(appData.results, f);
9997                 fclose(f); // release lock
9998                 return;
9999             }
10000         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10001     }
10002     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10003     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10004     free(participants);
10005     return;
10006 }
10007
10008 int
10009 CreateTourney (char *name)
10010 {
10011         FILE *f;
10012         if(matchMode && strcmp(name, appData.tourneyFile)) {
10013              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10014         }
10015         if(name[0] == NULLCHAR) {
10016             if(appData.participants[0])
10017                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10018             return 0;
10019         }
10020         f = fopen(name, "r");
10021         if(f) { // file exists
10022             ASSIGN(appData.tourneyFile, name);
10023             ParseArgsFromFile(f); // parse it
10024         } else {
10025             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10026             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10027                 DisplayError(_("Not enough participants"), 0);
10028                 return 0;
10029             }
10030             ASSIGN(appData.tourneyFile, name);
10031             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10032             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10033         }
10034         fclose(f);
10035         appData.noChessProgram = FALSE;
10036         appData.clockMode = TRUE;
10037         SetGNUMode();
10038         return 1;
10039 }
10040
10041 int
10042 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10043 {
10044     char buf[MSG_SIZ], *p, *q;
10045     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10046     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10047     skip = !all && group[0]; // if group requested, we start in skip mode
10048     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10049         p = names; q = buf; header = 0;
10050         while(*p && *p != '\n') *q++ = *p++;
10051         *q = 0;
10052         if(*p == '\n') p++;
10053         if(buf[0] == '#') {
10054             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10055             depth++; // we must be entering a new group
10056             if(all) continue; // suppress printing group headers when complete list requested
10057             header = 1;
10058             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10059         }
10060         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10061         if(engineList[i]) free(engineList[i]);
10062         engineList[i] = strdup(buf);
10063         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10064         if(engineMnemonic[i]) free(engineMnemonic[i]);
10065         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10066             strcat(buf, " (");
10067             sscanf(q + 8, "%s", buf + strlen(buf));
10068             strcat(buf, ")");
10069         }
10070         engineMnemonic[i] = strdup(buf);
10071         i++;
10072     }
10073     engineList[i] = engineMnemonic[i] = NULL;
10074     return i;
10075 }
10076
10077 // following implemented as macro to avoid type limitations
10078 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10079
10080 void
10081 SwapEngines (int n)
10082 {   // swap settings for first engine and other engine (so far only some selected options)
10083     int h;
10084     char *p;
10085     if(n == 0) return;
10086     SWAP(directory, p)
10087     SWAP(chessProgram, p)
10088     SWAP(isUCI, h)
10089     SWAP(hasOwnBookUCI, h)
10090     SWAP(protocolVersion, h)
10091     SWAP(reuse, h)
10092     SWAP(scoreIsAbsolute, h)
10093     SWAP(timeOdds, h)
10094     SWAP(logo, p)
10095     SWAP(pgnName, p)
10096     SWAP(pvSAN, h)
10097     SWAP(engOptions, p)
10098     SWAP(engInitString, p)
10099     SWAP(computerString, p)
10100     SWAP(features, p)
10101     SWAP(fenOverride, p)
10102     SWAP(NPS, h)
10103     SWAP(accumulateTC, h)
10104     SWAP(host, p)
10105 }
10106
10107 int
10108 SetPlayer (int player, char *p)
10109 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10110     int i;
10111     char buf[MSG_SIZ], *engineName;
10112     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10113     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10114     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10115     if(mnemonic[i]) {
10116         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10117         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10118         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10119         ParseArgsFromString(buf);
10120     }
10121     free(engineName);
10122     return i;
10123 }
10124
10125 char *recentEngines;
10126
10127 void
10128 RecentEngineEvent (int nr)
10129 {
10130     int n;
10131 //    SwapEngines(1); // bump first to second
10132 //    ReplaceEngine(&second, 1); // and load it there
10133     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10134     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10135     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10136         ReplaceEngine(&first, 0);
10137         FloatToFront(&appData.recentEngineList, command[n]);
10138     }
10139 }
10140
10141 int
10142 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10143 {   // determine players from game number
10144     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10145
10146     if(appData.tourneyType == 0) {
10147         roundsPerCycle = (nPlayers - 1) | 1;
10148         pairingsPerRound = nPlayers / 2;
10149     } else if(appData.tourneyType > 0) {
10150         roundsPerCycle = nPlayers - appData.tourneyType;
10151         pairingsPerRound = appData.tourneyType;
10152     }
10153     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10154     gamesPerCycle = gamesPerRound * roundsPerCycle;
10155     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10156     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10157     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10158     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10159     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10160     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10161
10162     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10163     if(appData.roundSync) *syncInterval = gamesPerRound;
10164
10165     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10166
10167     if(appData.tourneyType == 0) {
10168         if(curPairing == (nPlayers-1)/2 ) {
10169             *whitePlayer = curRound;
10170             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10171         } else {
10172             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10173             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10174             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10175             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10176         }
10177     } else if(appData.tourneyType > 1) {
10178         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10179         *whitePlayer = curRound + appData.tourneyType;
10180     } else if(appData.tourneyType > 0) {
10181         *whitePlayer = curPairing;
10182         *blackPlayer = curRound + appData.tourneyType;
10183     }
10184
10185     // take care of white/black alternation per round. 
10186     // For cycles and games this is already taken care of by default, derived from matchGame!
10187     return curRound & 1;
10188 }
10189
10190 int
10191 NextTourneyGame (int nr, int *swapColors)
10192 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10193     char *p, *q;
10194     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10195     FILE *tf;
10196     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10197     tf = fopen(appData.tourneyFile, "r");
10198     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10199     ParseArgsFromFile(tf); fclose(tf);
10200     InitTimeControls(); // TC might be altered from tourney file
10201
10202     nPlayers = CountPlayers(appData.participants); // count participants
10203     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10204     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10205
10206     if(syncInterval) {
10207         p = q = appData.results;
10208         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10209         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10210             DisplayMessage(_("Waiting for other game(s)"),"");
10211             waitingForGame = TRUE;
10212             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10213             return 0;
10214         }
10215         waitingForGame = FALSE;
10216     }
10217
10218     if(appData.tourneyType < 0) {
10219         if(nr>=0 && !pairingReceived) {
10220             char buf[1<<16];
10221             if(pairing.pr == NoProc) {
10222                 if(!appData.pairingEngine[0]) {
10223                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10224                     return 0;
10225                 }
10226                 StartChessProgram(&pairing); // starts the pairing engine
10227             }
10228             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10229             SendToProgram(buf, &pairing);
10230             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10231             SendToProgram(buf, &pairing);
10232             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10233         }
10234         pairingReceived = 0;                              // ... so we continue here 
10235         *swapColors = 0;
10236         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10237         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10238         matchGame = 1; roundNr = nr / syncInterval + 1;
10239     }
10240
10241     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10242
10243     // redefine engines, engine dir, etc.
10244     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10245     if(first.pr == NoProc) {
10246       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10247       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10248     }
10249     if(second.pr == NoProc) {
10250       SwapEngines(1);
10251       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10252       SwapEngines(1);         // and make that valid for second engine by swapping
10253       InitEngine(&second, 1);
10254     }
10255     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10256     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10257     return 1;
10258 }
10259
10260 void
10261 NextMatchGame ()
10262 {   // performs game initialization that does not invoke engines, and then tries to start the game
10263     int res, firstWhite, swapColors = 0;
10264     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10265     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
10266         char buf[MSG_SIZ];
10267         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10268         if(strcmp(buf, currentDebugFile)) { // name has changed
10269             FILE *f = fopen(buf, "w");
10270             if(f) { // if opening the new file failed, just keep using the old one
10271                 ASSIGN(currentDebugFile, buf);
10272                 fclose(debugFP);
10273                 debugFP = f;
10274             }
10275             if(appData.serverFileName) {
10276                 if(serverFP) fclose(serverFP);
10277                 serverFP = fopen(appData.serverFileName, "w");
10278                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10279                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10280             }
10281         }
10282     }
10283     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10284     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10285     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10286     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10287     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10288     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10289     Reset(FALSE, first.pr != NoProc);
10290     res = LoadGameOrPosition(matchGame); // setup game
10291     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10292     if(!res) return; // abort when bad game/pos file
10293     TwoMachinesEvent();
10294 }
10295
10296 void
10297 UserAdjudicationEvent (int result)
10298 {
10299     ChessMove gameResult = GameIsDrawn;
10300
10301     if( result > 0 ) {
10302         gameResult = WhiteWins;
10303     }
10304     else if( result < 0 ) {
10305         gameResult = BlackWins;
10306     }
10307
10308     if( gameMode == TwoMachinesPlay ) {
10309         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10310     }
10311 }
10312
10313
10314 // [HGM] save: calculate checksum of game to make games easily identifiable
10315 int
10316 StringCheckSum (char *s)
10317 {
10318         int i = 0;
10319         if(s==NULL) return 0;
10320         while(*s) i = i*259 + *s++;
10321         return i;
10322 }
10323
10324 int
10325 GameCheckSum ()
10326 {
10327         int i, sum=0;
10328         for(i=backwardMostMove; i<forwardMostMove; i++) {
10329                 sum += pvInfoList[i].depth;
10330                 sum += StringCheckSum(parseList[i]);
10331                 sum += StringCheckSum(commentList[i]);
10332                 sum *= 261;
10333         }
10334         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10335         return sum + StringCheckSum(commentList[i]);
10336 } // end of save patch
10337
10338 void
10339 GameEnds (ChessMove result, char *resultDetails, int whosays)
10340 {
10341     GameMode nextGameMode;
10342     int isIcsGame;
10343     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10344
10345     if(endingGame) return; /* [HGM] crash: forbid recursion */
10346     endingGame = 1;
10347     if(twoBoards) { // [HGM] dual: switch back to one board
10348         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10349         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10350     }
10351     if (appData.debugMode) {
10352       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10353               result, resultDetails ? resultDetails : "(null)", whosays);
10354     }
10355
10356     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10357
10358     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10359         /* If we are playing on ICS, the server decides when the
10360            game is over, but the engine can offer to draw, claim
10361            a draw, or resign.
10362          */
10363 #if ZIPPY
10364         if (appData.zippyPlay && first.initDone) {
10365             if (result == GameIsDrawn) {
10366                 /* In case draw still needs to be claimed */
10367                 SendToICS(ics_prefix);
10368                 SendToICS("draw\n");
10369             } else if (StrCaseStr(resultDetails, "resign")) {
10370                 SendToICS(ics_prefix);
10371                 SendToICS("resign\n");
10372             }
10373         }
10374 #endif
10375         endingGame = 0; /* [HGM] crash */
10376         return;
10377     }
10378
10379     /* If we're loading the game from a file, stop */
10380     if (whosays == GE_FILE) {
10381       (void) StopLoadGameTimer();
10382       gameFileFP = NULL;
10383     }
10384
10385     /* Cancel draw offers */
10386     first.offeredDraw = second.offeredDraw = 0;
10387
10388     /* If this is an ICS game, only ICS can really say it's done;
10389        if not, anyone can. */
10390     isIcsGame = (gameMode == IcsPlayingWhite ||
10391                  gameMode == IcsPlayingBlack ||
10392                  gameMode == IcsObserving    ||
10393                  gameMode == IcsExamining);
10394
10395     if (!isIcsGame || whosays == GE_ICS) {
10396         /* OK -- not an ICS game, or ICS said it was done */
10397         StopClocks();
10398         if (!isIcsGame && !appData.noChessProgram)
10399           SetUserThinkingEnables();
10400
10401         /* [HGM] if a machine claims the game end we verify this claim */
10402         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10403             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10404                 char claimer;
10405                 ChessMove trueResult = (ChessMove) -1;
10406
10407                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10408                                             first.twoMachinesColor[0] :
10409                                             second.twoMachinesColor[0] ;
10410
10411                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10412                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10413                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10414                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10415                 } else
10416                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10417                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10418                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10419                 } else
10420                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10421                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10422                 }
10423
10424                 // now verify win claims, but not in drop games, as we don't understand those yet
10425                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10426                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10427                     (result == WhiteWins && claimer == 'w' ||
10428                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10429                       if (appData.debugMode) {
10430                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10431                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10432                       }
10433                       if(result != trueResult) {
10434                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10435                               result = claimer == 'w' ? BlackWins : WhiteWins;
10436                               resultDetails = buf;
10437                       }
10438                 } else
10439                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10440                     && (forwardMostMove <= backwardMostMove ||
10441                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10442                         (claimer=='b')==(forwardMostMove&1))
10443                                                                                   ) {
10444                       /* [HGM] verify: draws that were not flagged are false claims */
10445                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10446                       result = claimer == 'w' ? BlackWins : WhiteWins;
10447                       resultDetails = buf;
10448                 }
10449                 /* (Claiming a loss is accepted no questions asked!) */
10450             }
10451             /* [HGM] bare: don't allow bare King to win */
10452             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10453                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10454                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10455                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10456                && result != GameIsDrawn)
10457             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10458                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10459                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10460                         if(p >= 0 && p <= (int)WhiteKing) k++;
10461                 }
10462                 if (appData.debugMode) {
10463                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10464                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10465                 }
10466                 if(k <= 1) {
10467                         result = GameIsDrawn;
10468                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10469                         resultDetails = buf;
10470                 }
10471             }
10472         }
10473
10474
10475         if(serverMoves != NULL && !loadFlag) { char c = '=';
10476             if(result==WhiteWins) c = '+';
10477             if(result==BlackWins) c = '-';
10478             if(resultDetails != NULL)
10479                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10480         }
10481         if (resultDetails != NULL) {
10482             gameInfo.result = result;
10483             gameInfo.resultDetails = StrSave(resultDetails);
10484
10485             /* display last move only if game was not loaded from file */
10486             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10487                 DisplayMove(currentMove - 1);
10488
10489             if (forwardMostMove != 0) {
10490                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10491                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10492                                                                 ) {
10493                     if (*appData.saveGameFile != NULLCHAR) {
10494                         SaveGameToFile(appData.saveGameFile, TRUE);
10495                     } else if (appData.autoSaveGames) {
10496                         AutoSaveGame();
10497                     }
10498                     if (*appData.savePositionFile != NULLCHAR) {
10499                         SavePositionToFile(appData.savePositionFile);
10500                     }
10501                 }
10502             }
10503
10504             /* Tell program how game ended in case it is learning */
10505             /* [HGM] Moved this to after saving the PGN, just in case */
10506             /* engine died and we got here through time loss. In that */
10507             /* case we will get a fatal error writing the pipe, which */
10508             /* would otherwise lose us the PGN.                       */
10509             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10510             /* output during GameEnds should never be fatal anymore   */
10511             if (gameMode == MachinePlaysWhite ||
10512                 gameMode == MachinePlaysBlack ||
10513                 gameMode == TwoMachinesPlay ||
10514                 gameMode == IcsPlayingWhite ||
10515                 gameMode == IcsPlayingBlack ||
10516                 gameMode == BeginningOfGame) {
10517                 char buf[MSG_SIZ];
10518                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10519                         resultDetails);
10520                 if (first.pr != NoProc) {
10521                     SendToProgram(buf, &first);
10522                 }
10523                 if (second.pr != NoProc &&
10524                     gameMode == TwoMachinesPlay) {
10525                     SendToProgram(buf, &second);
10526                 }
10527             }
10528         }
10529
10530         if (appData.icsActive) {
10531             if (appData.quietPlay &&
10532                 (gameMode == IcsPlayingWhite ||
10533                  gameMode == IcsPlayingBlack)) {
10534                 SendToICS(ics_prefix);
10535                 SendToICS("set shout 1\n");
10536             }
10537             nextGameMode = IcsIdle;
10538             ics_user_moved = FALSE;
10539             /* clean up premove.  It's ugly when the game has ended and the
10540              * premove highlights are still on the board.
10541              */
10542             if (gotPremove) {
10543               gotPremove = FALSE;
10544               ClearPremoveHighlights();
10545               DrawPosition(FALSE, boards[currentMove]);
10546             }
10547             if (whosays == GE_ICS) {
10548                 switch (result) {
10549                 case WhiteWins:
10550                     if (gameMode == IcsPlayingWhite)
10551                         PlayIcsWinSound();
10552                     else if(gameMode == IcsPlayingBlack)
10553                         PlayIcsLossSound();
10554                     break;
10555                 case BlackWins:
10556                     if (gameMode == IcsPlayingBlack)
10557                         PlayIcsWinSound();
10558                     else if(gameMode == IcsPlayingWhite)
10559                         PlayIcsLossSound();
10560                     break;
10561                 case GameIsDrawn:
10562                     PlayIcsDrawSound();
10563                     break;
10564                 default:
10565                     PlayIcsUnfinishedSound();
10566                 }
10567             }
10568         } else if (gameMode == EditGame ||
10569                    gameMode == PlayFromGameFile ||
10570                    gameMode == AnalyzeMode ||
10571                    gameMode == AnalyzeFile) {
10572             nextGameMode = gameMode;
10573         } else {
10574             nextGameMode = EndOfGame;
10575         }
10576         pausing = FALSE;
10577         ModeHighlight();
10578     } else {
10579         nextGameMode = gameMode;
10580     }
10581
10582     if (appData.noChessProgram) {
10583         gameMode = nextGameMode;
10584         ModeHighlight();
10585         endingGame = 0; /* [HGM] crash */
10586         return;
10587     }
10588
10589     if (first.reuse) {
10590         /* Put first chess program into idle state */
10591         if (first.pr != NoProc &&
10592             (gameMode == MachinePlaysWhite ||
10593              gameMode == MachinePlaysBlack ||
10594              gameMode == TwoMachinesPlay ||
10595              gameMode == IcsPlayingWhite ||
10596              gameMode == IcsPlayingBlack ||
10597              gameMode == BeginningOfGame)) {
10598             SendToProgram("force\n", &first);
10599             if (first.usePing) {
10600               char buf[MSG_SIZ];
10601               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10602               SendToProgram(buf, &first);
10603             }
10604         }
10605     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10606         /* Kill off first chess program */
10607         if (first.isr != NULL)
10608           RemoveInputSource(first.isr);
10609         first.isr = NULL;
10610
10611         if (first.pr != NoProc) {
10612             ExitAnalyzeMode();
10613             DoSleep( appData.delayBeforeQuit );
10614             SendToProgram("quit\n", &first);
10615             DoSleep( appData.delayAfterQuit );
10616             DestroyChildProcess(first.pr, first.useSigterm);
10617         }
10618         first.pr = NoProc;
10619     }
10620     if (second.reuse) {
10621         /* Put second chess program into idle state */
10622         if (second.pr != NoProc &&
10623             gameMode == TwoMachinesPlay) {
10624             SendToProgram("force\n", &second);
10625             if (second.usePing) {
10626               char buf[MSG_SIZ];
10627               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10628               SendToProgram(buf, &second);
10629             }
10630         }
10631     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10632         /* Kill off second chess program */
10633         if (second.isr != NULL)
10634           RemoveInputSource(second.isr);
10635         second.isr = NULL;
10636
10637         if (second.pr != NoProc) {
10638             DoSleep( appData.delayBeforeQuit );
10639             SendToProgram("quit\n", &second);
10640             DoSleep( appData.delayAfterQuit );
10641             DestroyChildProcess(second.pr, second.useSigterm);
10642         }
10643         second.pr = NoProc;
10644     }
10645
10646     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10647         char resChar = '=';
10648         switch (result) {
10649         case WhiteWins:
10650           resChar = '+';
10651           if (first.twoMachinesColor[0] == 'w') {
10652             first.matchWins++;
10653           } else {
10654             second.matchWins++;
10655           }
10656           break;
10657         case BlackWins:
10658           resChar = '-';
10659           if (first.twoMachinesColor[0] == 'b') {
10660             first.matchWins++;
10661           } else {
10662             second.matchWins++;
10663           }
10664           break;
10665         case GameUnfinished:
10666           resChar = ' ';
10667         default:
10668           break;
10669         }
10670
10671         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10672         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10673             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10674             ReserveGame(nextGame, resChar); // sets nextGame
10675             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10676             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10677         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10678
10679         if (nextGame <= appData.matchGames && !abortMatch) {
10680             gameMode = nextGameMode;
10681             matchGame = nextGame; // this will be overruled in tourney mode!
10682             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10683             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10684             endingGame = 0; /* [HGM] crash */
10685             return;
10686         } else {
10687             gameMode = nextGameMode;
10688             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10689                      first.tidy, second.tidy,
10690                      first.matchWins, second.matchWins,
10691                      appData.matchGames - (first.matchWins + second.matchWins));
10692             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10693             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10694             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10695             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10696                 first.twoMachinesColor = "black\n";
10697                 second.twoMachinesColor = "white\n";
10698             } else {
10699                 first.twoMachinesColor = "white\n";
10700                 second.twoMachinesColor = "black\n";
10701             }
10702         }
10703     }
10704     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10705         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10706       ExitAnalyzeMode();
10707     gameMode = nextGameMode;
10708     ModeHighlight();
10709     endingGame = 0;  /* [HGM] crash */
10710     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10711         if(matchMode == TRUE) { // match through command line: exit with or without popup
10712             if(ranking) {
10713                 ToNrEvent(forwardMostMove);
10714                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10715                 else ExitEvent(0);
10716             } else DisplayFatalError(buf, 0, 0);
10717         } else { // match through menu; just stop, with or without popup
10718             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10719             ModeHighlight();
10720             if(ranking){
10721                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10722             } else DisplayNote(buf);
10723       }
10724       if(ranking) free(ranking);
10725     }
10726 }
10727
10728 /* Assumes program was just initialized (initString sent).
10729    Leaves program in force mode. */
10730 void
10731 FeedMovesToProgram (ChessProgramState *cps, int upto)
10732 {
10733     int i;
10734
10735     if (appData.debugMode)
10736       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10737               startedFromSetupPosition ? "position and " : "",
10738               backwardMostMove, upto, cps->which);
10739     if(currentlyInitializedVariant != gameInfo.variant) {
10740       char buf[MSG_SIZ];
10741         // [HGM] variantswitch: make engine aware of new variant
10742         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10743                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10744         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10745         SendToProgram(buf, cps);
10746         currentlyInitializedVariant = gameInfo.variant;
10747     }
10748     SendToProgram("force\n", cps);
10749     if (startedFromSetupPosition) {
10750         SendBoard(cps, backwardMostMove);
10751     if (appData.debugMode) {
10752         fprintf(debugFP, "feedMoves\n");
10753     }
10754     }
10755     for (i = backwardMostMove; i < upto; i++) {
10756         SendMoveToProgram(i, cps);
10757     }
10758 }
10759
10760
10761 int
10762 ResurrectChessProgram ()
10763 {
10764      /* The chess program may have exited.
10765         If so, restart it and feed it all the moves made so far. */
10766     static int doInit = 0;
10767
10768     if (appData.noChessProgram) return 1;
10769
10770     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10771         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10772         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10773         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10774     } else {
10775         if (first.pr != NoProc) return 1;
10776         StartChessProgram(&first);
10777     }
10778     InitChessProgram(&first, FALSE);
10779     FeedMovesToProgram(&first, currentMove);
10780
10781     if (!first.sendTime) {
10782         /* can't tell gnuchess what its clock should read,
10783            so we bow to its notion. */
10784         ResetClocks();
10785         timeRemaining[0][currentMove] = whiteTimeRemaining;
10786         timeRemaining[1][currentMove] = blackTimeRemaining;
10787     }
10788
10789     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10790                 appData.icsEngineAnalyze) && first.analysisSupport) {
10791       SendToProgram("analyze\n", &first);
10792       first.analyzing = TRUE;
10793     }
10794     return 1;
10795 }
10796
10797 /*
10798  * Button procedures
10799  */
10800 void
10801 Reset (int redraw, int init)
10802 {
10803     int i;
10804
10805     if (appData.debugMode) {
10806         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10807                 redraw, init, gameMode);
10808     }
10809     CleanupTail(); // [HGM] vari: delete any stored variations
10810     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10811     pausing = pauseExamInvalid = FALSE;
10812     startedFromSetupPosition = blackPlaysFirst = FALSE;
10813     firstMove = TRUE;
10814     whiteFlag = blackFlag = FALSE;
10815     userOfferedDraw = FALSE;
10816     hintRequested = bookRequested = FALSE;
10817     first.maybeThinking = FALSE;
10818     second.maybeThinking = FALSE;
10819     first.bookSuspend = FALSE; // [HGM] book
10820     second.bookSuspend = FALSE;
10821     thinkOutput[0] = NULLCHAR;
10822     lastHint[0] = NULLCHAR;
10823     ClearGameInfo(&gameInfo);
10824     gameInfo.variant = StringToVariant(appData.variant);
10825     ics_user_moved = ics_clock_paused = FALSE;
10826     ics_getting_history = H_FALSE;
10827     ics_gamenum = -1;
10828     white_holding[0] = black_holding[0] = NULLCHAR;
10829     ClearProgramStats();
10830     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10831
10832     ResetFrontEnd();
10833     ClearHighlights();
10834     flipView = appData.flipView;
10835     ClearPremoveHighlights();
10836     gotPremove = FALSE;
10837     alarmSounded = FALSE;
10838
10839     GameEnds(EndOfFile, NULL, GE_PLAYER);
10840     if(appData.serverMovesName != NULL) {
10841         /* [HGM] prepare to make moves file for broadcasting */
10842         clock_t t = clock();
10843         if(serverMoves != NULL) fclose(serverMoves);
10844         serverMoves = fopen(appData.serverMovesName, "r");
10845         if(serverMoves != NULL) {
10846             fclose(serverMoves);
10847             /* delay 15 sec before overwriting, so all clients can see end */
10848             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10849         }
10850         serverMoves = fopen(appData.serverMovesName, "w");
10851     }
10852
10853     ExitAnalyzeMode();
10854     gameMode = BeginningOfGame;
10855     ModeHighlight();
10856     if(appData.icsActive) gameInfo.variant = VariantNormal;
10857     currentMove = forwardMostMove = backwardMostMove = 0;
10858     MarkTargetSquares(1);
10859     InitPosition(redraw);
10860     for (i = 0; i < MAX_MOVES; i++) {
10861         if (commentList[i] != NULL) {
10862             free(commentList[i]);
10863             commentList[i] = NULL;
10864         }
10865     }
10866     ResetClocks();
10867     timeRemaining[0][0] = whiteTimeRemaining;
10868     timeRemaining[1][0] = blackTimeRemaining;
10869
10870     if (first.pr == NoProc) {
10871         StartChessProgram(&first);
10872     }
10873     if (init) {
10874             InitChessProgram(&first, startedFromSetupPosition);
10875     }
10876     DisplayTitle("");
10877     DisplayMessage("", "");
10878     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10879     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10880     ClearMap();        // [HGM] exclude: invalidate map
10881 }
10882
10883 void
10884 AutoPlayGameLoop ()
10885 {
10886     for (;;) {
10887         if (!AutoPlayOneMove())
10888           return;
10889         if (matchMode || appData.timeDelay == 0)
10890           continue;
10891         if (appData.timeDelay < 0)
10892           return;
10893         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10894         break;
10895     }
10896 }
10897
10898
10899 int
10900 AutoPlayOneMove ()
10901 {
10902     int fromX, fromY, toX, toY;
10903
10904     if (appData.debugMode) {
10905       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10906     }
10907
10908     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10909       return FALSE;
10910
10911     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10912       pvInfoList[currentMove].depth = programStats.depth;
10913       pvInfoList[currentMove].score = programStats.score;
10914       pvInfoList[currentMove].time  = 0;
10915       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10916     }
10917
10918     if (currentMove >= forwardMostMove) {
10919       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10920 //      gameMode = EndOfGame;
10921 //      ModeHighlight();
10922
10923       /* [AS] Clear current move marker at the end of a game */
10924       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10925
10926       return FALSE;
10927     }
10928
10929     toX = moveList[currentMove][2] - AAA;
10930     toY = moveList[currentMove][3] - ONE;
10931
10932     if (moveList[currentMove][1] == '@') {
10933         if (appData.highlightLastMove) {
10934             SetHighlights(-1, -1, toX, toY);
10935         }
10936     } else {
10937         fromX = moveList[currentMove][0] - AAA;
10938         fromY = moveList[currentMove][1] - ONE;
10939
10940         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10941
10942         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10943
10944         if (appData.highlightLastMove) {
10945             SetHighlights(fromX, fromY, toX, toY);
10946         }
10947     }
10948     DisplayMove(currentMove);
10949     SendMoveToProgram(currentMove++, &first);
10950     DisplayBothClocks();
10951     DrawPosition(FALSE, boards[currentMove]);
10952     // [HGM] PV info: always display, routine tests if empty
10953     DisplayComment(currentMove - 1, commentList[currentMove]);
10954     return TRUE;
10955 }
10956
10957
10958 int
10959 LoadGameOneMove (ChessMove readAhead)
10960 {
10961     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10962     char promoChar = NULLCHAR;
10963     ChessMove moveType;
10964     char move[MSG_SIZ];
10965     char *p, *q;
10966
10967     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10968         gameMode != AnalyzeMode && gameMode != Training) {
10969         gameFileFP = NULL;
10970         return FALSE;
10971     }
10972
10973     yyboardindex = forwardMostMove;
10974     if (readAhead != EndOfFile) {
10975       moveType = readAhead;
10976     } else {
10977       if (gameFileFP == NULL)
10978           return FALSE;
10979       moveType = (ChessMove) Myylex();
10980     }
10981
10982     done = FALSE;
10983     switch (moveType) {
10984       case Comment:
10985         if (appData.debugMode)
10986           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10987         p = yy_text;
10988
10989         /* append the comment but don't display it */
10990         AppendComment(currentMove, p, FALSE);
10991         return TRUE;
10992
10993       case WhiteCapturesEnPassant:
10994       case BlackCapturesEnPassant:
10995       case WhitePromotion:
10996       case BlackPromotion:
10997       case WhiteNonPromotion:
10998       case BlackNonPromotion:
10999       case NormalMove:
11000       case WhiteKingSideCastle:
11001       case WhiteQueenSideCastle:
11002       case BlackKingSideCastle:
11003       case BlackQueenSideCastle:
11004       case WhiteKingSideCastleWild:
11005       case WhiteQueenSideCastleWild:
11006       case BlackKingSideCastleWild:
11007       case BlackQueenSideCastleWild:
11008       /* PUSH Fabien */
11009       case WhiteHSideCastleFR:
11010       case WhiteASideCastleFR:
11011       case BlackHSideCastleFR:
11012       case BlackASideCastleFR:
11013       /* POP Fabien */
11014         if (appData.debugMode)
11015           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11016         fromX = currentMoveString[0] - AAA;
11017         fromY = currentMoveString[1] - ONE;
11018         toX = currentMoveString[2] - AAA;
11019         toY = currentMoveString[3] - ONE;
11020         promoChar = currentMoveString[4];
11021         break;
11022
11023       case WhiteDrop:
11024       case BlackDrop:
11025         if (appData.debugMode)
11026           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11027         fromX = moveType == WhiteDrop ?
11028           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11029         (int) CharToPiece(ToLower(currentMoveString[0]));
11030         fromY = DROP_RANK;
11031         toX = currentMoveString[2] - AAA;
11032         toY = currentMoveString[3] - ONE;
11033         break;
11034
11035       case WhiteWins:
11036       case BlackWins:
11037       case GameIsDrawn:
11038       case GameUnfinished:
11039         if (appData.debugMode)
11040           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11041         p = strchr(yy_text, '{');
11042         if (p == NULL) p = strchr(yy_text, '(');
11043         if (p == NULL) {
11044             p = yy_text;
11045             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11046         } else {
11047             q = strchr(p, *p == '{' ? '}' : ')');
11048             if (q != NULL) *q = NULLCHAR;
11049             p++;
11050         }
11051         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11052         GameEnds(moveType, p, GE_FILE);
11053         done = TRUE;
11054         if (cmailMsgLoaded) {
11055             ClearHighlights();
11056             flipView = WhiteOnMove(currentMove);
11057             if (moveType == GameUnfinished) flipView = !flipView;
11058             if (appData.debugMode)
11059               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11060         }
11061         break;
11062
11063       case EndOfFile:
11064         if (appData.debugMode)
11065           fprintf(debugFP, "Parser hit end of file\n");
11066         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11067           case MT_NONE:
11068           case MT_CHECK:
11069             break;
11070           case MT_CHECKMATE:
11071           case MT_STAINMATE:
11072             if (WhiteOnMove(currentMove)) {
11073                 GameEnds(BlackWins, "Black mates", GE_FILE);
11074             } else {
11075                 GameEnds(WhiteWins, "White mates", GE_FILE);
11076             }
11077             break;
11078           case MT_STALEMATE:
11079             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11080             break;
11081         }
11082         done = TRUE;
11083         break;
11084
11085       case MoveNumberOne:
11086         if (lastLoadGameStart == GNUChessGame) {
11087             /* GNUChessGames have numbers, but they aren't move numbers */
11088             if (appData.debugMode)
11089               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11090                       yy_text, (int) moveType);
11091             return LoadGameOneMove(EndOfFile); /* tail recursion */
11092         }
11093         /* else fall thru */
11094
11095       case XBoardGame:
11096       case GNUChessGame:
11097       case PGNTag:
11098         /* Reached start of next game in file */
11099         if (appData.debugMode)
11100           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11101         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11102           case MT_NONE:
11103           case MT_CHECK:
11104             break;
11105           case MT_CHECKMATE:
11106           case MT_STAINMATE:
11107             if (WhiteOnMove(currentMove)) {
11108                 GameEnds(BlackWins, "Black mates", GE_FILE);
11109             } else {
11110                 GameEnds(WhiteWins, "White mates", GE_FILE);
11111             }
11112             break;
11113           case MT_STALEMATE:
11114             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11115             break;
11116         }
11117         done = TRUE;
11118         break;
11119
11120       case PositionDiagram:     /* should not happen; ignore */
11121       case ElapsedTime:         /* ignore */
11122       case NAG:                 /* ignore */
11123         if (appData.debugMode)
11124           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11125                   yy_text, (int) moveType);
11126         return LoadGameOneMove(EndOfFile); /* tail recursion */
11127
11128       case IllegalMove:
11129         if (appData.testLegality) {
11130             if (appData.debugMode)
11131               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11132             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11133                     (forwardMostMove / 2) + 1,
11134                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11135             DisplayError(move, 0);
11136             done = TRUE;
11137         } else {
11138             if (appData.debugMode)
11139               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11140                       yy_text, currentMoveString);
11141             fromX = currentMoveString[0] - AAA;
11142             fromY = currentMoveString[1] - ONE;
11143             toX = currentMoveString[2] - AAA;
11144             toY = currentMoveString[3] - ONE;
11145             promoChar = currentMoveString[4];
11146         }
11147         break;
11148
11149       case AmbiguousMove:
11150         if (appData.debugMode)
11151           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11152         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11153                 (forwardMostMove / 2) + 1,
11154                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11155         DisplayError(move, 0);
11156         done = TRUE;
11157         break;
11158
11159       default:
11160       case ImpossibleMove:
11161         if (appData.debugMode)
11162           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11163         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11164                 (forwardMostMove / 2) + 1,
11165                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11166         DisplayError(move, 0);
11167         done = TRUE;
11168         break;
11169     }
11170
11171     if (done) {
11172         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11173             DrawPosition(FALSE, boards[currentMove]);
11174             DisplayBothClocks();
11175             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11176               DisplayComment(currentMove - 1, commentList[currentMove]);
11177         }
11178         (void) StopLoadGameTimer();
11179         gameFileFP = NULL;
11180         cmailOldMove = forwardMostMove;
11181         return FALSE;
11182     } else {
11183         /* currentMoveString is set as a side-effect of yylex */
11184
11185         thinkOutput[0] = NULLCHAR;
11186         MakeMove(fromX, fromY, toX, toY, promoChar);
11187         currentMove = forwardMostMove;
11188         return TRUE;
11189     }
11190 }
11191
11192 /* Load the nth game from the given file */
11193 int
11194 LoadGameFromFile (char *filename, int n, char *title, int useList)
11195 {
11196     FILE *f;
11197     char buf[MSG_SIZ];
11198
11199     if (strcmp(filename, "-") == 0) {
11200         f = stdin;
11201         title = "stdin";
11202     } else {
11203         f = fopen(filename, "rb");
11204         if (f == NULL) {
11205           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11206             DisplayError(buf, errno);
11207             return FALSE;
11208         }
11209     }
11210     if (fseek(f, 0, 0) == -1) {
11211         /* f is not seekable; probably a pipe */
11212         useList = FALSE;
11213     }
11214     if (useList && n == 0) {
11215         int error = GameListBuild(f);
11216         if (error) {
11217             DisplayError(_("Cannot build game list"), error);
11218         } else if (!ListEmpty(&gameList) &&
11219                    ((ListGame *) gameList.tailPred)->number > 1) {
11220             GameListPopUp(f, title);
11221             return TRUE;
11222         }
11223         GameListDestroy();
11224         n = 1;
11225     }
11226     if (n == 0) n = 1;
11227     return LoadGame(f, n, title, FALSE);
11228 }
11229
11230
11231 void
11232 MakeRegisteredMove ()
11233 {
11234     int fromX, fromY, toX, toY;
11235     char promoChar;
11236     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11237         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11238           case CMAIL_MOVE:
11239           case CMAIL_DRAW:
11240             if (appData.debugMode)
11241               fprintf(debugFP, "Restoring %s for game %d\n",
11242                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11243
11244             thinkOutput[0] = NULLCHAR;
11245             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11246             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11247             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11248             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11249             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11250             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11251             MakeMove(fromX, fromY, toX, toY, promoChar);
11252             ShowMove(fromX, fromY, toX, toY);
11253
11254             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11255               case MT_NONE:
11256               case MT_CHECK:
11257                 break;
11258
11259               case MT_CHECKMATE:
11260               case MT_STAINMATE:
11261                 if (WhiteOnMove(currentMove)) {
11262                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11263                 } else {
11264                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11265                 }
11266                 break;
11267
11268               case MT_STALEMATE:
11269                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11270                 break;
11271             }
11272
11273             break;
11274
11275           case CMAIL_RESIGN:
11276             if (WhiteOnMove(currentMove)) {
11277                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11278             } else {
11279                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11280             }
11281             break;
11282
11283           case CMAIL_ACCEPT:
11284             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11285             break;
11286
11287           default:
11288             break;
11289         }
11290     }
11291
11292     return;
11293 }
11294
11295 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11296 int
11297 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11298 {
11299     int retVal;
11300
11301     if (gameNumber > nCmailGames) {
11302         DisplayError(_("No more games in this message"), 0);
11303         return FALSE;
11304     }
11305     if (f == lastLoadGameFP) {
11306         int offset = gameNumber - lastLoadGameNumber;
11307         if (offset == 0) {
11308             cmailMsg[0] = NULLCHAR;
11309             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11310                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11311                 nCmailMovesRegistered--;
11312             }
11313             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11314             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11315                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11316             }
11317         } else {
11318             if (! RegisterMove()) return FALSE;
11319         }
11320     }
11321
11322     retVal = LoadGame(f, gameNumber, title, useList);
11323
11324     /* Make move registered during previous look at this game, if any */
11325     MakeRegisteredMove();
11326
11327     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11328         commentList[currentMove]
11329           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11330         DisplayComment(currentMove - 1, commentList[currentMove]);
11331     }
11332
11333     return retVal;
11334 }
11335
11336 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11337 int
11338 ReloadGame (int offset)
11339 {
11340     int gameNumber = lastLoadGameNumber + offset;
11341     if (lastLoadGameFP == NULL) {
11342         DisplayError(_("No game has been loaded yet"), 0);
11343         return FALSE;
11344     }
11345     if (gameNumber <= 0) {
11346         DisplayError(_("Can't back up any further"), 0);
11347         return FALSE;
11348     }
11349     if (cmailMsgLoaded) {
11350         return CmailLoadGame(lastLoadGameFP, gameNumber,
11351                              lastLoadGameTitle, lastLoadGameUseList);
11352     } else {
11353         return LoadGame(lastLoadGameFP, gameNumber,
11354                         lastLoadGameTitle, lastLoadGameUseList);
11355     }
11356 }
11357
11358 int keys[EmptySquare+1];
11359
11360 int
11361 PositionMatches (Board b1, Board b2)
11362 {
11363     int r, f, sum=0;
11364     switch(appData.searchMode) {
11365         case 1: return CompareWithRights(b1, b2);
11366         case 2:
11367             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11368                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11369             }
11370             return TRUE;
11371         case 3:
11372             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11373               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11374                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11375             }
11376             return sum==0;
11377         case 4:
11378             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11379                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11380             }
11381             return sum==0;
11382     }
11383     return TRUE;
11384 }
11385
11386 #define Q_PROMO  4
11387 #define Q_EP     3
11388 #define Q_BCASTL 2
11389 #define Q_WCASTL 1
11390
11391 int pieceList[256], quickBoard[256];
11392 ChessSquare pieceType[256] = { EmptySquare };
11393 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11394 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11395 int soughtTotal, turn;
11396 Boolean epOK, flipSearch;
11397
11398 typedef struct {
11399     unsigned char piece, to;
11400 } Move;
11401
11402 #define DSIZE (250000)
11403
11404 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11405 Move *moveDatabase = initialSpace;
11406 unsigned int movePtr, dataSize = DSIZE;
11407
11408 int
11409 MakePieceList (Board board, int *counts)
11410 {
11411     int r, f, n=Q_PROMO, total=0;
11412     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11413     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11414         int sq = f + (r<<4);
11415         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11416             quickBoard[sq] = ++n;
11417             pieceList[n] = sq;
11418             pieceType[n] = board[r][f];
11419             counts[board[r][f]]++;
11420             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11421             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11422             total++;
11423         }
11424     }
11425     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11426     return total;
11427 }
11428
11429 void
11430 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11431 {
11432     int sq = fromX + (fromY<<4);
11433     int piece = quickBoard[sq];
11434     quickBoard[sq] = 0;
11435     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11436     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11437         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11438         moveDatabase[movePtr++].piece = Q_WCASTL;
11439         quickBoard[sq] = piece;
11440         piece = quickBoard[from]; quickBoard[from] = 0;
11441         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11442     } else
11443     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11444         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11445         moveDatabase[movePtr++].piece = Q_BCASTL;
11446         quickBoard[sq] = piece;
11447         piece = quickBoard[from]; quickBoard[from] = 0;
11448         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11449     } else
11450     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11451         quickBoard[(fromY<<4)+toX] = 0;
11452         moveDatabase[movePtr].piece = Q_EP;
11453         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11454         moveDatabase[movePtr].to = sq;
11455     } else
11456     if(promoPiece != pieceType[piece]) {
11457         moveDatabase[movePtr++].piece = Q_PROMO;
11458         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11459     }
11460     moveDatabase[movePtr].piece = piece;
11461     quickBoard[sq] = piece;
11462     movePtr++;
11463 }
11464
11465 int
11466 PackGame (Board board)
11467 {
11468     Move *newSpace = NULL;
11469     moveDatabase[movePtr].piece = 0; // terminate previous game
11470     if(movePtr > dataSize) {
11471         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11472         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11473         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11474         if(newSpace) {
11475             int i;
11476             Move *p = moveDatabase, *q = newSpace;
11477             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11478             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11479             moveDatabase = newSpace;
11480         } else { // calloc failed, we must be out of memory. Too bad...
11481             dataSize = 0; // prevent calloc events for all subsequent games
11482             return 0;     // and signal this one isn't cached
11483         }
11484     }
11485     movePtr++;
11486     MakePieceList(board, counts);
11487     return movePtr;
11488 }
11489
11490 int
11491 QuickCompare (Board board, int *minCounts, int *maxCounts)
11492 {   // compare according to search mode
11493     int r, f;
11494     switch(appData.searchMode)
11495     {
11496       case 1: // exact position match
11497         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11498         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11499             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11500         }
11501         break;
11502       case 2: // can have extra material on empty squares
11503         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11504             if(board[r][f] == EmptySquare) continue;
11505             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11506         }
11507         break;
11508       case 3: // material with exact Pawn structure
11509         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11510             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11511             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11512         } // fall through to material comparison
11513       case 4: // exact material
11514         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11515         break;
11516       case 6: // material range with given imbalance
11517         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11518         // fall through to range comparison
11519       case 5: // material range
11520         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11521     }
11522     return TRUE;
11523 }
11524
11525 int
11526 QuickScan (Board board, Move *move)
11527 {   // reconstruct game,and compare all positions in it
11528     int cnt=0, stretch=0, total = MakePieceList(board, counts), delayedKing = -1;
11529     do {
11530         int piece = move->piece;
11531         int to = move->to, from = pieceList[piece];
11532         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11533           if(!piece) return -1;
11534           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11535             piece = (++move)->piece;
11536             from = pieceList[piece];
11537             counts[pieceType[piece]]--;
11538             pieceType[piece] = (ChessSquare) move->to;
11539             counts[move->to]++;
11540           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11541             counts[pieceType[quickBoard[to]]]--;
11542             quickBoard[to] = 0; total--;
11543             move++;
11544             continue;
11545           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11546             int rook;
11547             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11548             from  = pieceList[piece]; // so this must be King
11549             quickBoard[from] = 0;
11550             pieceList[piece] = to;
11551             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11552             quickBoard[from] = 0; // rook
11553             quickBoard[to] = piece;
11554             to = move->to; piece = move->piece;
11555             goto aftercastle;
11556           }
11557         }
11558         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11559         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11560         quickBoard[from] = 0;
11561       aftercastle:
11562         quickBoard[to] = piece;
11563         pieceList[piece] = to;
11564         cnt++; turn ^= 3;
11565         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11566            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11567            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11568                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11569           ) {
11570             static int lastCounts[EmptySquare+1];
11571             int i;
11572             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11573             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11574         } else stretch = 0;
11575         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11576         move++; delayedKing = -1;
11577     } while(1);
11578 }
11579
11580 void
11581 InitSearch ()
11582 {
11583     int r, f;
11584     flipSearch = FALSE;
11585     CopyBoard(soughtBoard, boards[currentMove]);
11586     soughtTotal = MakePieceList(soughtBoard, maxSought);
11587     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11588     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11589     CopyBoard(reverseBoard, boards[currentMove]);
11590     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11591         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11592         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11593         reverseBoard[r][f] = piece;
11594     }
11595     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11596     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11597     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11598                  || (boards[currentMove][CASTLING][2] == NoRights || 
11599                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11600                  && (boards[currentMove][CASTLING][5] == NoRights || 
11601                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11602       ) {
11603         flipSearch = TRUE;
11604         CopyBoard(flipBoard, soughtBoard);
11605         CopyBoard(rotateBoard, reverseBoard);
11606         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11607             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11608             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11609         }
11610     }
11611     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11612     if(appData.searchMode >= 5) {
11613         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11614         MakePieceList(soughtBoard, minSought);
11615         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11616     }
11617     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11618         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11619 }
11620
11621 GameInfo dummyInfo;
11622
11623 int
11624 GameContainsPosition (FILE *f, ListGame *lg)
11625 {
11626     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11627     int fromX, fromY, toX, toY;
11628     char promoChar;
11629     static int initDone=FALSE;
11630
11631     // weed out games based on numerical tag comparison
11632     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11633     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11634     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11635     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11636     if(!initDone) {
11637         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11638         initDone = TRUE;
11639     }
11640     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11641     else CopyBoard(boards[scratch], initialPosition); // default start position
11642     if(lg->moves) {
11643         turn = btm + 1;
11644         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11645         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11646     }
11647     if(btm) plyNr++;
11648     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11649     fseek(f, lg->offset, 0);
11650     yynewfile(f);
11651     while(1) {
11652         yyboardindex = scratch;
11653         quickFlag = plyNr+1;
11654         next = Myylex();
11655         quickFlag = 0;
11656         switch(next) {
11657             case PGNTag:
11658                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11659             default:
11660                 continue;
11661
11662             case XBoardGame:
11663             case GNUChessGame:
11664                 if(plyNr) return -1; // after we have seen moves, this is for new game
11665               continue;
11666
11667             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11668             case ImpossibleMove:
11669             case WhiteWins: // game ends here with these four
11670             case BlackWins:
11671             case GameIsDrawn:
11672             case GameUnfinished:
11673                 return -1;
11674
11675             case IllegalMove:
11676                 if(appData.testLegality) return -1;
11677             case WhiteCapturesEnPassant:
11678             case BlackCapturesEnPassant:
11679             case WhitePromotion:
11680             case BlackPromotion:
11681             case WhiteNonPromotion:
11682             case BlackNonPromotion:
11683             case NormalMove:
11684             case WhiteKingSideCastle:
11685             case WhiteQueenSideCastle:
11686             case BlackKingSideCastle:
11687             case BlackQueenSideCastle:
11688             case WhiteKingSideCastleWild:
11689             case WhiteQueenSideCastleWild:
11690             case BlackKingSideCastleWild:
11691             case BlackQueenSideCastleWild:
11692             case WhiteHSideCastleFR:
11693             case WhiteASideCastleFR:
11694             case BlackHSideCastleFR:
11695             case BlackASideCastleFR:
11696                 fromX = currentMoveString[0] - AAA;
11697                 fromY = currentMoveString[1] - ONE;
11698                 toX = currentMoveString[2] - AAA;
11699                 toY = currentMoveString[3] - ONE;
11700                 promoChar = currentMoveString[4];
11701                 break;
11702             case WhiteDrop:
11703             case BlackDrop:
11704                 fromX = next == WhiteDrop ?
11705                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11706                   (int) CharToPiece(ToLower(currentMoveString[0]));
11707                 fromY = DROP_RANK;
11708                 toX = currentMoveString[2] - AAA;
11709                 toY = currentMoveString[3] - ONE;
11710                 promoChar = 0;
11711                 break;
11712         }
11713         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11714         plyNr++;
11715         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11716         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11717         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11718         if(appData.findMirror) {
11719             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11720             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11721         }
11722     }
11723 }
11724
11725 /* Load the nth game from open file f */
11726 int
11727 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11728 {
11729     ChessMove cm;
11730     char buf[MSG_SIZ];
11731     int gn = gameNumber;
11732     ListGame *lg = NULL;
11733     int numPGNTags = 0;
11734     int err, pos = -1;
11735     GameMode oldGameMode;
11736     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11737
11738     if (appData.debugMode)
11739         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11740
11741     if (gameMode == Training )
11742         SetTrainingModeOff();
11743
11744     oldGameMode = gameMode;
11745     if (gameMode != BeginningOfGame) {
11746       Reset(FALSE, TRUE);
11747     }
11748
11749     gameFileFP = f;
11750     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11751         fclose(lastLoadGameFP);
11752     }
11753
11754     if (useList) {
11755         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11756
11757         if (lg) {
11758             fseek(f, lg->offset, 0);
11759             GameListHighlight(gameNumber);
11760             pos = lg->position;
11761             gn = 1;
11762         }
11763         else {
11764             DisplayError(_("Game number out of range"), 0);
11765             return FALSE;
11766         }
11767     } else {
11768         GameListDestroy();
11769         if (fseek(f, 0, 0) == -1) {
11770             if (f == lastLoadGameFP ?
11771                 gameNumber == lastLoadGameNumber + 1 :
11772                 gameNumber == 1) {
11773                 gn = 1;
11774             } else {
11775                 DisplayError(_("Can't seek on game file"), 0);
11776                 return FALSE;
11777             }
11778         }
11779     }
11780     lastLoadGameFP = f;
11781     lastLoadGameNumber = gameNumber;
11782     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11783     lastLoadGameUseList = useList;
11784
11785     yynewfile(f);
11786
11787     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11788       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11789                 lg->gameInfo.black);
11790             DisplayTitle(buf);
11791     } else if (*title != NULLCHAR) {
11792         if (gameNumber > 1) {
11793           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11794             DisplayTitle(buf);
11795         } else {
11796             DisplayTitle(title);
11797         }
11798     }
11799
11800     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11801         gameMode = PlayFromGameFile;
11802         ModeHighlight();
11803     }
11804
11805     currentMove = forwardMostMove = backwardMostMove = 0;
11806     CopyBoard(boards[0], initialPosition);
11807     StopClocks();
11808
11809     /*
11810      * Skip the first gn-1 games in the file.
11811      * Also skip over anything that precedes an identifiable
11812      * start of game marker, to avoid being confused by
11813      * garbage at the start of the file.  Currently
11814      * recognized start of game markers are the move number "1",
11815      * the pattern "gnuchess .* game", the pattern
11816      * "^[#;%] [^ ]* game file", and a PGN tag block.
11817      * A game that starts with one of the latter two patterns
11818      * will also have a move number 1, possibly
11819      * following a position diagram.
11820      * 5-4-02: Let's try being more lenient and allowing a game to
11821      * start with an unnumbered move.  Does that break anything?
11822      */
11823     cm = lastLoadGameStart = EndOfFile;
11824     while (gn > 0) {
11825         yyboardindex = forwardMostMove;
11826         cm = (ChessMove) Myylex();
11827         switch (cm) {
11828           case EndOfFile:
11829             if (cmailMsgLoaded) {
11830                 nCmailGames = CMAIL_MAX_GAMES - gn;
11831             } else {
11832                 Reset(TRUE, TRUE);
11833                 DisplayError(_("Game not found in file"), 0);
11834             }
11835             return FALSE;
11836
11837           case GNUChessGame:
11838           case XBoardGame:
11839             gn--;
11840             lastLoadGameStart = cm;
11841             break;
11842
11843           case MoveNumberOne:
11844             switch (lastLoadGameStart) {
11845               case GNUChessGame:
11846               case XBoardGame:
11847               case PGNTag:
11848                 break;
11849               case MoveNumberOne:
11850               case EndOfFile:
11851                 gn--;           /* count this game */
11852                 lastLoadGameStart = cm;
11853                 break;
11854               default:
11855                 /* impossible */
11856                 break;
11857             }
11858             break;
11859
11860           case PGNTag:
11861             switch (lastLoadGameStart) {
11862               case GNUChessGame:
11863               case PGNTag:
11864               case MoveNumberOne:
11865               case EndOfFile:
11866                 gn--;           /* count this game */
11867                 lastLoadGameStart = cm;
11868                 break;
11869               case XBoardGame:
11870                 lastLoadGameStart = cm; /* game counted already */
11871                 break;
11872               default:
11873                 /* impossible */
11874                 break;
11875             }
11876             if (gn > 0) {
11877                 do {
11878                     yyboardindex = forwardMostMove;
11879                     cm = (ChessMove) Myylex();
11880                 } while (cm == PGNTag || cm == Comment);
11881             }
11882             break;
11883
11884           case WhiteWins:
11885           case BlackWins:
11886           case GameIsDrawn:
11887             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11888                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11889                     != CMAIL_OLD_RESULT) {
11890                     nCmailResults ++ ;
11891                     cmailResult[  CMAIL_MAX_GAMES
11892                                 - gn - 1] = CMAIL_OLD_RESULT;
11893                 }
11894             }
11895             break;
11896
11897           case NormalMove:
11898             /* Only a NormalMove can be at the start of a game
11899              * without a position diagram. */
11900             if (lastLoadGameStart == EndOfFile ) {
11901               gn--;
11902               lastLoadGameStart = MoveNumberOne;
11903             }
11904             break;
11905
11906           default:
11907             break;
11908         }
11909     }
11910
11911     if (appData.debugMode)
11912       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11913
11914     if (cm == XBoardGame) {
11915         /* Skip any header junk before position diagram and/or move 1 */
11916         for (;;) {
11917             yyboardindex = forwardMostMove;
11918             cm = (ChessMove) Myylex();
11919
11920             if (cm == EndOfFile ||
11921                 cm == GNUChessGame || cm == XBoardGame) {
11922                 /* Empty game; pretend end-of-file and handle later */
11923                 cm = EndOfFile;
11924                 break;
11925             }
11926
11927             if (cm == MoveNumberOne || cm == PositionDiagram ||
11928                 cm == PGNTag || cm == Comment)
11929               break;
11930         }
11931     } else if (cm == GNUChessGame) {
11932         if (gameInfo.event != NULL) {
11933             free(gameInfo.event);
11934         }
11935         gameInfo.event = StrSave(yy_text);
11936     }
11937
11938     startedFromSetupPosition = FALSE;
11939     while (cm == PGNTag) {
11940         if (appData.debugMode)
11941           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11942         err = ParsePGNTag(yy_text, &gameInfo);
11943         if (!err) numPGNTags++;
11944
11945         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11946         if(gameInfo.variant != oldVariant) {
11947             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11948             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11949             InitPosition(TRUE);
11950             oldVariant = gameInfo.variant;
11951             if (appData.debugMode)
11952               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11953         }
11954
11955
11956         if (gameInfo.fen != NULL) {
11957           Board initial_position;
11958           startedFromSetupPosition = TRUE;
11959           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11960             Reset(TRUE, TRUE);
11961             DisplayError(_("Bad FEN position in file"), 0);
11962             return FALSE;
11963           }
11964           CopyBoard(boards[0], initial_position);
11965           if (blackPlaysFirst) {
11966             currentMove = forwardMostMove = backwardMostMove = 1;
11967             CopyBoard(boards[1], initial_position);
11968             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11969             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11970             timeRemaining[0][1] = whiteTimeRemaining;
11971             timeRemaining[1][1] = blackTimeRemaining;
11972             if (commentList[0] != NULL) {
11973               commentList[1] = commentList[0];
11974               commentList[0] = NULL;
11975             }
11976           } else {
11977             currentMove = forwardMostMove = backwardMostMove = 0;
11978           }
11979           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11980           {   int i;
11981               initialRulePlies = FENrulePlies;
11982               for( i=0; i< nrCastlingRights; i++ )
11983                   initialRights[i] = initial_position[CASTLING][i];
11984           }
11985           yyboardindex = forwardMostMove;
11986           free(gameInfo.fen);
11987           gameInfo.fen = NULL;
11988         }
11989
11990         yyboardindex = forwardMostMove;
11991         cm = (ChessMove) Myylex();
11992
11993         /* Handle comments interspersed among the tags */
11994         while (cm == Comment) {
11995             char *p;
11996             if (appData.debugMode)
11997               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11998             p = yy_text;
11999             AppendComment(currentMove, p, FALSE);
12000             yyboardindex = forwardMostMove;
12001             cm = (ChessMove) Myylex();
12002         }
12003     }
12004
12005     /* don't rely on existence of Event tag since if game was
12006      * pasted from clipboard the Event tag may not exist
12007      */
12008     if (numPGNTags > 0){
12009         char *tags;
12010         if (gameInfo.variant == VariantNormal) {
12011           VariantClass v = StringToVariant(gameInfo.event);
12012           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12013           if(v < VariantShogi) gameInfo.variant = v;
12014         }
12015         if (!matchMode) {
12016           if( appData.autoDisplayTags ) {
12017             tags = PGNTags(&gameInfo);
12018             TagsPopUp(tags, CmailMsg());
12019             free(tags);
12020           }
12021         }
12022     } else {
12023         /* Make something up, but don't display it now */
12024         SetGameInfo();
12025         TagsPopDown();
12026     }
12027
12028     if (cm == PositionDiagram) {
12029         int i, j;
12030         char *p;
12031         Board initial_position;
12032
12033         if (appData.debugMode)
12034           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12035
12036         if (!startedFromSetupPosition) {
12037             p = yy_text;
12038             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12039               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12040                 switch (*p) {
12041                   case '{':
12042                   case '[':
12043                   case '-':
12044                   case ' ':
12045                   case '\t':
12046                   case '\n':
12047                   case '\r':
12048                     break;
12049                   default:
12050                     initial_position[i][j++] = CharToPiece(*p);
12051                     break;
12052                 }
12053             while (*p == ' ' || *p == '\t' ||
12054                    *p == '\n' || *p == '\r') p++;
12055
12056             if (strncmp(p, "black", strlen("black"))==0)
12057               blackPlaysFirst = TRUE;
12058             else
12059               blackPlaysFirst = FALSE;
12060             startedFromSetupPosition = TRUE;
12061
12062             CopyBoard(boards[0], initial_position);
12063             if (blackPlaysFirst) {
12064                 currentMove = forwardMostMove = backwardMostMove = 1;
12065                 CopyBoard(boards[1], initial_position);
12066                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12067                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12068                 timeRemaining[0][1] = whiteTimeRemaining;
12069                 timeRemaining[1][1] = blackTimeRemaining;
12070                 if (commentList[0] != NULL) {
12071                     commentList[1] = commentList[0];
12072                     commentList[0] = NULL;
12073                 }
12074             } else {
12075                 currentMove = forwardMostMove = backwardMostMove = 0;
12076             }
12077         }
12078         yyboardindex = forwardMostMove;
12079         cm = (ChessMove) Myylex();
12080     }
12081
12082     if (first.pr == NoProc) {
12083         StartChessProgram(&first);
12084     }
12085     InitChessProgram(&first, FALSE);
12086     SendToProgram("force\n", &first);
12087     if (startedFromSetupPosition) {
12088         SendBoard(&first, forwardMostMove);
12089     if (appData.debugMode) {
12090         fprintf(debugFP, "Load Game\n");
12091     }
12092         DisplayBothClocks();
12093     }
12094
12095     /* [HGM] server: flag to write setup moves in broadcast file as one */
12096     loadFlag = appData.suppressLoadMoves;
12097
12098     while (cm == Comment) {
12099         char *p;
12100         if (appData.debugMode)
12101           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12102         p = yy_text;
12103         AppendComment(currentMove, p, FALSE);
12104         yyboardindex = forwardMostMove;
12105         cm = (ChessMove) Myylex();
12106     }
12107
12108     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12109         cm == WhiteWins || cm == BlackWins ||
12110         cm == GameIsDrawn || cm == GameUnfinished) {
12111         DisplayMessage("", _("No moves in game"));
12112         if (cmailMsgLoaded) {
12113             if (appData.debugMode)
12114               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12115             ClearHighlights();
12116             flipView = FALSE;
12117         }
12118         DrawPosition(FALSE, boards[currentMove]);
12119         DisplayBothClocks();
12120         gameMode = EditGame;
12121         ModeHighlight();
12122         gameFileFP = NULL;
12123         cmailOldMove = 0;
12124         return TRUE;
12125     }
12126
12127     // [HGM] PV info: routine tests if comment empty
12128     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12129         DisplayComment(currentMove - 1, commentList[currentMove]);
12130     }
12131     if (!matchMode && appData.timeDelay != 0)
12132       DrawPosition(FALSE, boards[currentMove]);
12133
12134     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12135       programStats.ok_to_send = 1;
12136     }
12137
12138     /* if the first token after the PGN tags is a move
12139      * and not move number 1, retrieve it from the parser
12140      */
12141     if (cm != MoveNumberOne)
12142         LoadGameOneMove(cm);
12143
12144     /* load the remaining moves from the file */
12145     while (LoadGameOneMove(EndOfFile)) {
12146       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12147       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12148     }
12149
12150     /* rewind to the start of the game */
12151     currentMove = backwardMostMove;
12152
12153     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12154
12155     if (oldGameMode == AnalyzeFile ||
12156         oldGameMode == AnalyzeMode) {
12157       AnalyzeFileEvent();
12158     }
12159
12160     if (!matchMode && pos > 0) {
12161         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12162     } else
12163     if (matchMode || appData.timeDelay == 0) {
12164       ToEndEvent();
12165     } else if (appData.timeDelay > 0) {
12166       AutoPlayGameLoop();
12167     }
12168
12169     if (appData.debugMode)
12170         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12171
12172     loadFlag = 0; /* [HGM] true game starts */
12173     return TRUE;
12174 }
12175
12176 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12177 int
12178 ReloadPosition (int offset)
12179 {
12180     int positionNumber = lastLoadPositionNumber + offset;
12181     if (lastLoadPositionFP == NULL) {
12182         DisplayError(_("No position has been loaded yet"), 0);
12183         return FALSE;
12184     }
12185     if (positionNumber <= 0) {
12186         DisplayError(_("Can't back up any further"), 0);
12187         return FALSE;
12188     }
12189     return LoadPosition(lastLoadPositionFP, positionNumber,
12190                         lastLoadPositionTitle);
12191 }
12192
12193 /* Load the nth position from the given file */
12194 int
12195 LoadPositionFromFile (char *filename, int n, char *title)
12196 {
12197     FILE *f;
12198     char buf[MSG_SIZ];
12199
12200     if (strcmp(filename, "-") == 0) {
12201         return LoadPosition(stdin, n, "stdin");
12202     } else {
12203         f = fopen(filename, "rb");
12204         if (f == NULL) {
12205             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12206             DisplayError(buf, errno);
12207             return FALSE;
12208         } else {
12209             return LoadPosition(f, n, title);
12210         }
12211     }
12212 }
12213
12214 /* Load the nth position from the given open file, and close it */
12215 int
12216 LoadPosition (FILE *f, int positionNumber, char *title)
12217 {
12218     char *p, line[MSG_SIZ];
12219     Board initial_position;
12220     int i, j, fenMode, pn;
12221
12222     if (gameMode == Training )
12223         SetTrainingModeOff();
12224
12225     if (gameMode != BeginningOfGame) {
12226         Reset(FALSE, TRUE);
12227     }
12228     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12229         fclose(lastLoadPositionFP);
12230     }
12231     if (positionNumber == 0) positionNumber = 1;
12232     lastLoadPositionFP = f;
12233     lastLoadPositionNumber = positionNumber;
12234     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12235     if (first.pr == NoProc && !appData.noChessProgram) {
12236       StartChessProgram(&first);
12237       InitChessProgram(&first, FALSE);
12238     }
12239     pn = positionNumber;
12240     if (positionNumber < 0) {
12241         /* Negative position number means to seek to that byte offset */
12242         if (fseek(f, -positionNumber, 0) == -1) {
12243             DisplayError(_("Can't seek on position file"), 0);
12244             return FALSE;
12245         };
12246         pn = 1;
12247     } else {
12248         if (fseek(f, 0, 0) == -1) {
12249             if (f == lastLoadPositionFP ?
12250                 positionNumber == lastLoadPositionNumber + 1 :
12251                 positionNumber == 1) {
12252                 pn = 1;
12253             } else {
12254                 DisplayError(_("Can't seek on position file"), 0);
12255                 return FALSE;
12256             }
12257         }
12258     }
12259     /* See if this file is FEN or old-style xboard */
12260     if (fgets(line, MSG_SIZ, f) == NULL) {
12261         DisplayError(_("Position not found in file"), 0);
12262         return FALSE;
12263     }
12264     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12265     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12266
12267     if (pn >= 2) {
12268         if (fenMode || line[0] == '#') pn--;
12269         while (pn > 0) {
12270             /* skip positions before number pn */
12271             if (fgets(line, MSG_SIZ, f) == NULL) {
12272                 Reset(TRUE, TRUE);
12273                 DisplayError(_("Position not found in file"), 0);
12274                 return FALSE;
12275             }
12276             if (fenMode || line[0] == '#') pn--;
12277         }
12278     }
12279
12280     if (fenMode) {
12281         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12282             DisplayError(_("Bad FEN position in file"), 0);
12283             return FALSE;
12284         }
12285     } else {
12286         (void) fgets(line, MSG_SIZ, f);
12287         (void) fgets(line, MSG_SIZ, f);
12288
12289         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12290             (void) fgets(line, MSG_SIZ, f);
12291             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12292                 if (*p == ' ')
12293                   continue;
12294                 initial_position[i][j++] = CharToPiece(*p);
12295             }
12296         }
12297
12298         blackPlaysFirst = FALSE;
12299         if (!feof(f)) {
12300             (void) fgets(line, MSG_SIZ, f);
12301             if (strncmp(line, "black", strlen("black"))==0)
12302               blackPlaysFirst = TRUE;
12303         }
12304     }
12305     startedFromSetupPosition = TRUE;
12306
12307     CopyBoard(boards[0], initial_position);
12308     if (blackPlaysFirst) {
12309         currentMove = forwardMostMove = backwardMostMove = 1;
12310         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12311         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12312         CopyBoard(boards[1], initial_position);
12313         DisplayMessage("", _("Black to play"));
12314     } else {
12315         currentMove = forwardMostMove = backwardMostMove = 0;
12316         DisplayMessage("", _("White to play"));
12317     }
12318     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12319     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12320         SendToProgram("force\n", &first);
12321         SendBoard(&first, forwardMostMove);
12322     }
12323     if (appData.debugMode) {
12324 int i, j;
12325   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12326   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12327         fprintf(debugFP, "Load Position\n");
12328     }
12329
12330     if (positionNumber > 1) {
12331       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12332         DisplayTitle(line);
12333     } else {
12334         DisplayTitle(title);
12335     }
12336     gameMode = EditGame;
12337     ModeHighlight();
12338     ResetClocks();
12339     timeRemaining[0][1] = whiteTimeRemaining;
12340     timeRemaining[1][1] = blackTimeRemaining;
12341     DrawPosition(FALSE, boards[currentMove]);
12342
12343     return TRUE;
12344 }
12345
12346
12347 void
12348 CopyPlayerNameIntoFileName (char **dest, char *src)
12349 {
12350     while (*src != NULLCHAR && *src != ',') {
12351         if (*src == ' ') {
12352             *(*dest)++ = '_';
12353             src++;
12354         } else {
12355             *(*dest)++ = *src++;
12356         }
12357     }
12358 }
12359
12360 char *
12361 DefaultFileName (char *ext)
12362 {
12363     static char def[MSG_SIZ];
12364     char *p;
12365
12366     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12367         p = def;
12368         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12369         *p++ = '-';
12370         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12371         *p++ = '.';
12372         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12373     } else {
12374         def[0] = NULLCHAR;
12375     }
12376     return def;
12377 }
12378
12379 /* Save the current game to the given file */
12380 int
12381 SaveGameToFile (char *filename, int append)
12382 {
12383     FILE *f;
12384     char buf[MSG_SIZ];
12385     int result, i, t,tot=0;
12386
12387     if (strcmp(filename, "-") == 0) {
12388         return SaveGame(stdout, 0, NULL);
12389     } else {
12390         for(i=0; i<10; i++) { // upto 10 tries
12391              f = fopen(filename, append ? "a" : "w");
12392              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12393              if(f || errno != 13) break;
12394              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12395              tot += t;
12396         }
12397         if (f == NULL) {
12398             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12399             DisplayError(buf, errno);
12400             return FALSE;
12401         } else {
12402             safeStrCpy(buf, lastMsg, MSG_SIZ);
12403             DisplayMessage(_("Waiting for access to save file"), "");
12404             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12405             DisplayMessage(_("Saving game"), "");
12406             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12407             result = SaveGame(f, 0, NULL);
12408             DisplayMessage(buf, "");
12409             return result;
12410         }
12411     }
12412 }
12413
12414 char *
12415 SavePart (char *str)
12416 {
12417     static char buf[MSG_SIZ];
12418     char *p;
12419
12420     p = strchr(str, ' ');
12421     if (p == NULL) return str;
12422     strncpy(buf, str, p - str);
12423     buf[p - str] = NULLCHAR;
12424     return buf;
12425 }
12426
12427 #define PGN_MAX_LINE 75
12428
12429 #define PGN_SIDE_WHITE  0
12430 #define PGN_SIDE_BLACK  1
12431
12432 static int
12433 FindFirstMoveOutOfBook (int side)
12434 {
12435     int result = -1;
12436
12437     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12438         int index = backwardMostMove;
12439         int has_book_hit = 0;
12440
12441         if( (index % 2) != side ) {
12442             index++;
12443         }
12444
12445         while( index < forwardMostMove ) {
12446             /* Check to see if engine is in book */
12447             int depth = pvInfoList[index].depth;
12448             int score = pvInfoList[index].score;
12449             int in_book = 0;
12450
12451             if( depth <= 2 ) {
12452                 in_book = 1;
12453             }
12454             else if( score == 0 && depth == 63 ) {
12455                 in_book = 1; /* Zappa */
12456             }
12457             else if( score == 2 && depth == 99 ) {
12458                 in_book = 1; /* Abrok */
12459             }
12460
12461             has_book_hit += in_book;
12462
12463             if( ! in_book ) {
12464                 result = index;
12465
12466                 break;
12467             }
12468
12469             index += 2;
12470         }
12471     }
12472
12473     return result;
12474 }
12475
12476 void
12477 GetOutOfBookInfo (char * buf)
12478 {
12479     int oob[2];
12480     int i;
12481     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12482
12483     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12484     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12485
12486     *buf = '\0';
12487
12488     if( oob[0] >= 0 || oob[1] >= 0 ) {
12489         for( i=0; i<2; i++ ) {
12490             int idx = oob[i];
12491
12492             if( idx >= 0 ) {
12493                 if( i > 0 && oob[0] >= 0 ) {
12494                     strcat( buf, "   " );
12495                 }
12496
12497                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12498                 sprintf( buf+strlen(buf), "%s%.2f",
12499                     pvInfoList[idx].score >= 0 ? "+" : "",
12500                     pvInfoList[idx].score / 100.0 );
12501             }
12502         }
12503     }
12504 }
12505
12506 /* Save game in PGN style and close the file */
12507 int
12508 SaveGamePGN (FILE *f)
12509 {
12510     int i, offset, linelen, newblock;
12511     time_t tm;
12512 //    char *movetext;
12513     char numtext[32];
12514     int movelen, numlen, blank;
12515     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12516
12517     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12518
12519     tm = time((time_t *) NULL);
12520
12521     PrintPGNTags(f, &gameInfo);
12522
12523     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12524
12525     if (backwardMostMove > 0 || startedFromSetupPosition) {
12526         char *fen = PositionToFEN(backwardMostMove, NULL);
12527         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12528         fprintf(f, "\n{--------------\n");
12529         PrintPosition(f, backwardMostMove);
12530         fprintf(f, "--------------}\n");
12531         free(fen);
12532     }
12533     else {
12534         /* [AS] Out of book annotation */
12535         if( appData.saveOutOfBookInfo ) {
12536             char buf[64];
12537
12538             GetOutOfBookInfo( buf );
12539
12540             if( buf[0] != '\0' ) {
12541                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12542             }
12543         }
12544
12545         fprintf(f, "\n");
12546     }
12547
12548     i = backwardMostMove;
12549     linelen = 0;
12550     newblock = TRUE;
12551
12552     while (i < forwardMostMove) {
12553         /* Print comments preceding this move */
12554         if (commentList[i] != NULL) {
12555             if (linelen > 0) fprintf(f, "\n");
12556             fprintf(f, "%s", commentList[i]);
12557             linelen = 0;
12558             newblock = TRUE;
12559         }
12560
12561         /* Format move number */
12562         if ((i % 2) == 0)
12563           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12564         else
12565           if (newblock)
12566             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12567           else
12568             numtext[0] = NULLCHAR;
12569
12570         numlen = strlen(numtext);
12571         newblock = FALSE;
12572
12573         /* Print move number */
12574         blank = linelen > 0 && numlen > 0;
12575         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12576             fprintf(f, "\n");
12577             linelen = 0;
12578             blank = 0;
12579         }
12580         if (blank) {
12581             fprintf(f, " ");
12582             linelen++;
12583         }
12584         fprintf(f, "%s", numtext);
12585         linelen += numlen;
12586
12587         /* Get move */
12588         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12589         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12590
12591         /* Print move */
12592         blank = linelen > 0 && movelen > 0;
12593         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12594             fprintf(f, "\n");
12595             linelen = 0;
12596             blank = 0;
12597         }
12598         if (blank) {
12599             fprintf(f, " ");
12600             linelen++;
12601         }
12602         fprintf(f, "%s", move_buffer);
12603         linelen += movelen;
12604
12605         /* [AS] Add PV info if present */
12606         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12607             /* [HGM] add time */
12608             char buf[MSG_SIZ]; int seconds;
12609
12610             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12611
12612             if( seconds <= 0)
12613               buf[0] = 0;
12614             else
12615               if( seconds < 30 )
12616                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12617               else
12618                 {
12619                   seconds = (seconds + 4)/10; // round to full seconds
12620                   if( seconds < 60 )
12621                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12622                   else
12623                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12624                 }
12625
12626             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12627                       pvInfoList[i].score >= 0 ? "+" : "",
12628                       pvInfoList[i].score / 100.0,
12629                       pvInfoList[i].depth,
12630                       buf );
12631
12632             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12633
12634             /* Print score/depth */
12635             blank = linelen > 0 && movelen > 0;
12636             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12637                 fprintf(f, "\n");
12638                 linelen = 0;
12639                 blank = 0;
12640             }
12641             if (blank) {
12642                 fprintf(f, " ");
12643                 linelen++;
12644             }
12645             fprintf(f, "%s", move_buffer);
12646             linelen += movelen;
12647         }
12648
12649         i++;
12650     }
12651
12652     /* Start a new line */
12653     if (linelen > 0) fprintf(f, "\n");
12654
12655     /* Print comments after last move */
12656     if (commentList[i] != NULL) {
12657         fprintf(f, "%s\n", commentList[i]);
12658     }
12659
12660     /* Print result */
12661     if (gameInfo.resultDetails != NULL &&
12662         gameInfo.resultDetails[0] != NULLCHAR) {
12663         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12664                 PGNResult(gameInfo.result));
12665     } else {
12666         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12667     }
12668
12669     fclose(f);
12670     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12671     return TRUE;
12672 }
12673
12674 /* Save game in old style and close the file */
12675 int
12676 SaveGameOldStyle (FILE *f)
12677 {
12678     int i, offset;
12679     time_t tm;
12680
12681     tm = time((time_t *) NULL);
12682
12683     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12684     PrintOpponents(f);
12685
12686     if (backwardMostMove > 0 || startedFromSetupPosition) {
12687         fprintf(f, "\n[--------------\n");
12688         PrintPosition(f, backwardMostMove);
12689         fprintf(f, "--------------]\n");
12690     } else {
12691         fprintf(f, "\n");
12692     }
12693
12694     i = backwardMostMove;
12695     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12696
12697     while (i < forwardMostMove) {
12698         if (commentList[i] != NULL) {
12699             fprintf(f, "[%s]\n", commentList[i]);
12700         }
12701
12702         if ((i % 2) == 1) {
12703             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12704             i++;
12705         } else {
12706             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12707             i++;
12708             if (commentList[i] != NULL) {
12709                 fprintf(f, "\n");
12710                 continue;
12711             }
12712             if (i >= forwardMostMove) {
12713                 fprintf(f, "\n");
12714                 break;
12715             }
12716             fprintf(f, "%s\n", parseList[i]);
12717             i++;
12718         }
12719     }
12720
12721     if (commentList[i] != NULL) {
12722         fprintf(f, "[%s]\n", commentList[i]);
12723     }
12724
12725     /* This isn't really the old style, but it's close enough */
12726     if (gameInfo.resultDetails != NULL &&
12727         gameInfo.resultDetails[0] != NULLCHAR) {
12728         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12729                 gameInfo.resultDetails);
12730     } else {
12731         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12732     }
12733
12734     fclose(f);
12735     return TRUE;
12736 }
12737
12738 /* Save the current game to open file f and close the file */
12739 int
12740 SaveGame (FILE *f, int dummy, char *dummy2)
12741 {
12742     if (gameMode == EditPosition) EditPositionDone(TRUE);
12743     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12744     if (appData.oldSaveStyle)
12745       return SaveGameOldStyle(f);
12746     else
12747       return SaveGamePGN(f);
12748 }
12749
12750 /* Save the current position to the given file */
12751 int
12752 SavePositionToFile (char *filename)
12753 {
12754     FILE *f;
12755     char buf[MSG_SIZ];
12756
12757     if (strcmp(filename, "-") == 0) {
12758         return SavePosition(stdout, 0, NULL);
12759     } else {
12760         f = fopen(filename, "a");
12761         if (f == NULL) {
12762             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12763             DisplayError(buf, errno);
12764             return FALSE;
12765         } else {
12766             safeStrCpy(buf, lastMsg, MSG_SIZ);
12767             DisplayMessage(_("Waiting for access to save file"), "");
12768             flock(fileno(f), LOCK_EX); // [HGM] lock
12769             DisplayMessage(_("Saving position"), "");
12770             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12771             SavePosition(f, 0, NULL);
12772             DisplayMessage(buf, "");
12773             return TRUE;
12774         }
12775     }
12776 }
12777
12778 /* Save the current position to the given open file and close the file */
12779 int
12780 SavePosition (FILE *f, int dummy, char *dummy2)
12781 {
12782     time_t tm;
12783     char *fen;
12784
12785     if (gameMode == EditPosition) EditPositionDone(TRUE);
12786     if (appData.oldSaveStyle) {
12787         tm = time((time_t *) NULL);
12788
12789         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12790         PrintOpponents(f);
12791         fprintf(f, "[--------------\n");
12792         PrintPosition(f, currentMove);
12793         fprintf(f, "--------------]\n");
12794     } else {
12795         fen = PositionToFEN(currentMove, NULL);
12796         fprintf(f, "%s\n", fen);
12797         free(fen);
12798     }
12799     fclose(f);
12800     return TRUE;
12801 }
12802
12803 void
12804 ReloadCmailMsgEvent (int unregister)
12805 {
12806 #if !WIN32
12807     static char *inFilename = NULL;
12808     static char *outFilename;
12809     int i;
12810     struct stat inbuf, outbuf;
12811     int status;
12812
12813     /* Any registered moves are unregistered if unregister is set, */
12814     /* i.e. invoked by the signal handler */
12815     if (unregister) {
12816         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12817             cmailMoveRegistered[i] = FALSE;
12818             if (cmailCommentList[i] != NULL) {
12819                 free(cmailCommentList[i]);
12820                 cmailCommentList[i] = NULL;
12821             }
12822         }
12823         nCmailMovesRegistered = 0;
12824     }
12825
12826     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12827         cmailResult[i] = CMAIL_NOT_RESULT;
12828     }
12829     nCmailResults = 0;
12830
12831     if (inFilename == NULL) {
12832         /* Because the filenames are static they only get malloced once  */
12833         /* and they never get freed                                      */
12834         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12835         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12836
12837         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12838         sprintf(outFilename, "%s.out", appData.cmailGameName);
12839     }
12840
12841     status = stat(outFilename, &outbuf);
12842     if (status < 0) {
12843         cmailMailedMove = FALSE;
12844     } else {
12845         status = stat(inFilename, &inbuf);
12846         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12847     }
12848
12849     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12850        counts the games, notes how each one terminated, etc.
12851
12852        It would be nice to remove this kludge and instead gather all
12853        the information while building the game list.  (And to keep it
12854        in the game list nodes instead of having a bunch of fixed-size
12855        parallel arrays.)  Note this will require getting each game's
12856        termination from the PGN tags, as the game list builder does
12857        not process the game moves.  --mann
12858        */
12859     cmailMsgLoaded = TRUE;
12860     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12861
12862     /* Load first game in the file or popup game menu */
12863     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12864
12865 #endif /* !WIN32 */
12866     return;
12867 }
12868
12869 int
12870 RegisterMove ()
12871 {
12872     FILE *f;
12873     char string[MSG_SIZ];
12874
12875     if (   cmailMailedMove
12876         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12877         return TRUE;            /* Allow free viewing  */
12878     }
12879
12880     /* Unregister move to ensure that we don't leave RegisterMove        */
12881     /* with the move registered when the conditions for registering no   */
12882     /* longer hold                                                       */
12883     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12884         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12885         nCmailMovesRegistered --;
12886
12887         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12888           {
12889               free(cmailCommentList[lastLoadGameNumber - 1]);
12890               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12891           }
12892     }
12893
12894     if (cmailOldMove == -1) {
12895         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12896         return FALSE;
12897     }
12898
12899     if (currentMove > cmailOldMove + 1) {
12900         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12901         return FALSE;
12902     }
12903
12904     if (currentMove < cmailOldMove) {
12905         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12906         return FALSE;
12907     }
12908
12909     if (forwardMostMove > currentMove) {
12910         /* Silently truncate extra moves */
12911         TruncateGame();
12912     }
12913
12914     if (   (currentMove == cmailOldMove + 1)
12915         || (   (currentMove == cmailOldMove)
12916             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12917                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12918         if (gameInfo.result != GameUnfinished) {
12919             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12920         }
12921
12922         if (commentList[currentMove] != NULL) {
12923             cmailCommentList[lastLoadGameNumber - 1]
12924               = StrSave(commentList[currentMove]);
12925         }
12926         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12927
12928         if (appData.debugMode)
12929           fprintf(debugFP, "Saving %s for game %d\n",
12930                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12931
12932         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12933
12934         f = fopen(string, "w");
12935         if (appData.oldSaveStyle) {
12936             SaveGameOldStyle(f); /* also closes the file */
12937
12938             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12939             f = fopen(string, "w");
12940             SavePosition(f, 0, NULL); /* also closes the file */
12941         } else {
12942             fprintf(f, "{--------------\n");
12943             PrintPosition(f, currentMove);
12944             fprintf(f, "--------------}\n\n");
12945
12946             SaveGame(f, 0, NULL); /* also closes the file*/
12947         }
12948
12949         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12950         nCmailMovesRegistered ++;
12951     } else if (nCmailGames == 1) {
12952         DisplayError(_("You have not made a move yet"), 0);
12953         return FALSE;
12954     }
12955
12956     return TRUE;
12957 }
12958
12959 void
12960 MailMoveEvent ()
12961 {
12962 #if !WIN32
12963     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12964     FILE *commandOutput;
12965     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12966     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12967     int nBuffers;
12968     int i;
12969     int archived;
12970     char *arcDir;
12971
12972     if (! cmailMsgLoaded) {
12973         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12974         return;
12975     }
12976
12977     if (nCmailGames == nCmailResults) {
12978         DisplayError(_("No unfinished games"), 0);
12979         return;
12980     }
12981
12982 #if CMAIL_PROHIBIT_REMAIL
12983     if (cmailMailedMove) {
12984       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);
12985         DisplayError(msg, 0);
12986         return;
12987     }
12988 #endif
12989
12990     if (! (cmailMailedMove || RegisterMove())) return;
12991
12992     if (   cmailMailedMove
12993         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12994       snprintf(string, MSG_SIZ, partCommandString,
12995                appData.debugMode ? " -v" : "", appData.cmailGameName);
12996         commandOutput = popen(string, "r");
12997
12998         if (commandOutput == NULL) {
12999             DisplayError(_("Failed to invoke cmail"), 0);
13000         } else {
13001             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13002                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13003             }
13004             if (nBuffers > 1) {
13005                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13006                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13007                 nBytes = MSG_SIZ - 1;
13008             } else {
13009                 (void) memcpy(msg, buffer, nBytes);
13010             }
13011             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13012
13013             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13014                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13015
13016                 archived = TRUE;
13017                 for (i = 0; i < nCmailGames; i ++) {
13018                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13019                         archived = FALSE;
13020                     }
13021                 }
13022                 if (   archived
13023                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13024                         != NULL)) {
13025                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13026                            arcDir,
13027                            appData.cmailGameName,
13028                            gameInfo.date);
13029                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13030                     cmailMsgLoaded = FALSE;
13031                 }
13032             }
13033
13034             DisplayInformation(msg);
13035             pclose(commandOutput);
13036         }
13037     } else {
13038         if ((*cmailMsg) != '\0') {
13039             DisplayInformation(cmailMsg);
13040         }
13041     }
13042
13043     return;
13044 #endif /* !WIN32 */
13045 }
13046
13047 char *
13048 CmailMsg ()
13049 {
13050 #if WIN32
13051     return NULL;
13052 #else
13053     int  prependComma = 0;
13054     char number[5];
13055     char string[MSG_SIZ];       /* Space for game-list */
13056     int  i;
13057
13058     if (!cmailMsgLoaded) return "";
13059
13060     if (cmailMailedMove) {
13061       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13062     } else {
13063         /* Create a list of games left */
13064       snprintf(string, MSG_SIZ, "[");
13065         for (i = 0; i < nCmailGames; i ++) {
13066             if (! (   cmailMoveRegistered[i]
13067                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13068                 if (prependComma) {
13069                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13070                 } else {
13071                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13072                     prependComma = 1;
13073                 }
13074
13075                 strcat(string, number);
13076             }
13077         }
13078         strcat(string, "]");
13079
13080         if (nCmailMovesRegistered + nCmailResults == 0) {
13081             switch (nCmailGames) {
13082               case 1:
13083                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13084                 break;
13085
13086               case 2:
13087                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13088                 break;
13089
13090               default:
13091                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13092                          nCmailGames);
13093                 break;
13094             }
13095         } else {
13096             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13097               case 1:
13098                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13099                          string);
13100                 break;
13101
13102               case 0:
13103                 if (nCmailResults == nCmailGames) {
13104                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13105                 } else {
13106                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13107                 }
13108                 break;
13109
13110               default:
13111                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13112                          string);
13113             }
13114         }
13115     }
13116     return cmailMsg;
13117 #endif /* WIN32 */
13118 }
13119
13120 void
13121 ResetGameEvent ()
13122 {
13123     if (gameMode == Training)
13124       SetTrainingModeOff();
13125
13126     Reset(TRUE, TRUE);
13127     cmailMsgLoaded = FALSE;
13128     if (appData.icsActive) {
13129       SendToICS(ics_prefix);
13130       SendToICS("refresh\n");
13131     }
13132 }
13133
13134 void
13135 ExitEvent (int status)
13136 {
13137     exiting++;
13138     if (exiting > 2) {
13139       /* Give up on clean exit */
13140       exit(status);
13141     }
13142     if (exiting > 1) {
13143       /* Keep trying for clean exit */
13144       return;
13145     }
13146
13147     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13148
13149     if (telnetISR != NULL) {
13150       RemoveInputSource(telnetISR);
13151     }
13152     if (icsPR != NoProc) {
13153       DestroyChildProcess(icsPR, TRUE);
13154     }
13155
13156     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13157     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13158
13159     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13160     /* make sure this other one finishes before killing it!                  */
13161     if(endingGame) { int count = 0;
13162         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13163         while(endingGame && count++ < 10) DoSleep(1);
13164         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13165     }
13166
13167     /* Kill off chess programs */
13168     if (first.pr != NoProc) {
13169         ExitAnalyzeMode();
13170
13171         DoSleep( appData.delayBeforeQuit );
13172         SendToProgram("quit\n", &first);
13173         DoSleep( appData.delayAfterQuit );
13174         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13175     }
13176     if (second.pr != NoProc) {
13177         DoSleep( appData.delayBeforeQuit );
13178         SendToProgram("quit\n", &second);
13179         DoSleep( appData.delayAfterQuit );
13180         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13181     }
13182     if (first.isr != NULL) {
13183         RemoveInputSource(first.isr);
13184     }
13185     if (second.isr != NULL) {
13186         RemoveInputSource(second.isr);
13187     }
13188
13189     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13190     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13191
13192     ShutDownFrontEnd();
13193     exit(status);
13194 }
13195
13196 void
13197 PauseEvent ()
13198 {
13199     if (appData.debugMode)
13200         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13201     if (pausing) {
13202         pausing = FALSE;
13203         ModeHighlight();
13204         if (gameMode == MachinePlaysWhite ||
13205             gameMode == MachinePlaysBlack) {
13206             StartClocks();
13207         } else {
13208             DisplayBothClocks();
13209         }
13210         if (gameMode == PlayFromGameFile) {
13211             if (appData.timeDelay >= 0)
13212                 AutoPlayGameLoop();
13213         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13214             Reset(FALSE, TRUE);
13215             SendToICS(ics_prefix);
13216             SendToICS("refresh\n");
13217         } else if (currentMove < forwardMostMove) {
13218             ForwardInner(forwardMostMove);
13219         }
13220         pauseExamInvalid = FALSE;
13221     } else {
13222         switch (gameMode) {
13223           default:
13224             return;
13225           case IcsExamining:
13226             pauseExamForwardMostMove = forwardMostMove;
13227             pauseExamInvalid = FALSE;
13228             /* fall through */
13229           case IcsObserving:
13230           case IcsPlayingWhite:
13231           case IcsPlayingBlack:
13232             pausing = TRUE;
13233             ModeHighlight();
13234             return;
13235           case PlayFromGameFile:
13236             (void) StopLoadGameTimer();
13237             pausing = TRUE;
13238             ModeHighlight();
13239             break;
13240           case BeginningOfGame:
13241             if (appData.icsActive) return;
13242             /* else fall through */
13243           case MachinePlaysWhite:
13244           case MachinePlaysBlack:
13245           case TwoMachinesPlay:
13246             if (forwardMostMove == 0)
13247               return;           /* don't pause if no one has moved */
13248             if ((gameMode == MachinePlaysWhite &&
13249                  !WhiteOnMove(forwardMostMove)) ||
13250                 (gameMode == MachinePlaysBlack &&
13251                  WhiteOnMove(forwardMostMove))) {
13252                 StopClocks();
13253             }
13254             pausing = TRUE;
13255             ModeHighlight();
13256             break;
13257         }
13258     }
13259 }
13260
13261 void
13262 EditCommentEvent ()
13263 {
13264     char title[MSG_SIZ];
13265
13266     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13267       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13268     } else {
13269       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13270                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13271                parseList[currentMove - 1]);
13272     }
13273
13274     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13275 }
13276
13277
13278 void
13279 EditTagsEvent ()
13280 {
13281     char *tags = PGNTags(&gameInfo);
13282     bookUp = FALSE;
13283     EditTagsPopUp(tags, NULL);
13284     free(tags);
13285 }
13286
13287 void
13288 AnalyzeModeEvent ()
13289 {
13290     if (appData.noChessProgram || gameMode == AnalyzeMode)
13291       return;
13292
13293     if (gameMode != AnalyzeFile) {
13294         if (!appData.icsEngineAnalyze) {
13295                EditGameEvent();
13296                if (gameMode != EditGame) return;
13297         }
13298         ResurrectChessProgram();
13299         SendToProgram("analyze\n", &first);
13300         first.analyzing = TRUE;
13301         /*first.maybeThinking = TRUE;*/
13302         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13303         EngineOutputPopUp();
13304     }
13305     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13306     pausing = FALSE;
13307     ModeHighlight();
13308     SetGameInfo();
13309
13310     StartAnalysisClock();
13311     GetTimeMark(&lastNodeCountTime);
13312     lastNodeCount = 0;
13313 }
13314
13315 void
13316 AnalyzeFileEvent ()
13317 {
13318     if (appData.noChessProgram || gameMode == AnalyzeFile)
13319       return;
13320
13321     if (gameMode != AnalyzeMode) {
13322         EditGameEvent();
13323         if (gameMode != EditGame) return;
13324         ResurrectChessProgram();
13325         SendToProgram("analyze\n", &first);
13326         first.analyzing = TRUE;
13327         /*first.maybeThinking = TRUE;*/
13328         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13329         EngineOutputPopUp();
13330     }
13331     gameMode = AnalyzeFile;
13332     pausing = FALSE;
13333     ModeHighlight();
13334     SetGameInfo();
13335
13336     StartAnalysisClock();
13337     GetTimeMark(&lastNodeCountTime);
13338     lastNodeCount = 0;
13339     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13340 }
13341
13342 void
13343 MachineWhiteEvent ()
13344 {
13345     char buf[MSG_SIZ];
13346     char *bookHit = NULL;
13347
13348     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13349       return;
13350
13351
13352     if (gameMode == PlayFromGameFile ||
13353         gameMode == TwoMachinesPlay  ||
13354         gameMode == Training         ||
13355         gameMode == AnalyzeMode      ||
13356         gameMode == EndOfGame)
13357         EditGameEvent();
13358
13359     if (gameMode == EditPosition)
13360         EditPositionDone(TRUE);
13361
13362     if (!WhiteOnMove(currentMove)) {
13363         DisplayError(_("It is not White's turn"), 0);
13364         return;
13365     }
13366
13367     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13368       ExitAnalyzeMode();
13369
13370     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13371         gameMode == AnalyzeFile)
13372         TruncateGame();
13373
13374     ResurrectChessProgram();    /* in case it isn't running */
13375     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13376         gameMode = MachinePlaysWhite;
13377         ResetClocks();
13378     } else
13379     gameMode = MachinePlaysWhite;
13380     pausing = FALSE;
13381     ModeHighlight();
13382     SetGameInfo();
13383     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13384     DisplayTitle(buf);
13385     if (first.sendName) {
13386       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13387       SendToProgram(buf, &first);
13388     }
13389     if (first.sendTime) {
13390       if (first.useColors) {
13391         SendToProgram("black\n", &first); /*gnu kludge*/
13392       }
13393       SendTimeRemaining(&first, TRUE);
13394     }
13395     if (first.useColors) {
13396       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13397     }
13398     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13399     SetMachineThinkingEnables();
13400     first.maybeThinking = TRUE;
13401     StartClocks();
13402     firstMove = FALSE;
13403
13404     if (appData.autoFlipView && !flipView) {
13405       flipView = !flipView;
13406       DrawPosition(FALSE, NULL);
13407       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13408     }
13409
13410     if(bookHit) { // [HGM] book: simulate book reply
13411         static char bookMove[MSG_SIZ]; // a bit generous?
13412
13413         programStats.nodes = programStats.depth = programStats.time =
13414         programStats.score = programStats.got_only_move = 0;
13415         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13416
13417         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13418         strcat(bookMove, bookHit);
13419         HandleMachineMove(bookMove, &first);
13420     }
13421 }
13422
13423 void
13424 MachineBlackEvent ()
13425 {
13426   char buf[MSG_SIZ];
13427   char *bookHit = NULL;
13428
13429     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13430         return;
13431
13432
13433     if (gameMode == PlayFromGameFile ||
13434         gameMode == TwoMachinesPlay  ||
13435         gameMode == Training         ||
13436         gameMode == AnalyzeMode      ||
13437         gameMode == EndOfGame)
13438         EditGameEvent();
13439
13440     if (gameMode == EditPosition)
13441         EditPositionDone(TRUE);
13442
13443     if (WhiteOnMove(currentMove)) {
13444         DisplayError(_("It is not Black's turn"), 0);
13445         return;
13446     }
13447
13448     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13449       ExitAnalyzeMode();
13450
13451     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13452         gameMode == AnalyzeFile)
13453         TruncateGame();
13454
13455     ResurrectChessProgram();    /* in case it isn't running */
13456     gameMode = MachinePlaysBlack;
13457     pausing = FALSE;
13458     ModeHighlight();
13459     SetGameInfo();
13460     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13461     DisplayTitle(buf);
13462     if (first.sendName) {
13463       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13464       SendToProgram(buf, &first);
13465     }
13466     if (first.sendTime) {
13467       if (first.useColors) {
13468         SendToProgram("white\n", &first); /*gnu kludge*/
13469       }
13470       SendTimeRemaining(&first, FALSE);
13471     }
13472     if (first.useColors) {
13473       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13474     }
13475     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13476     SetMachineThinkingEnables();
13477     first.maybeThinking = TRUE;
13478     StartClocks();
13479
13480     if (appData.autoFlipView && flipView) {
13481       flipView = !flipView;
13482       DrawPosition(FALSE, NULL);
13483       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13484     }
13485     if(bookHit) { // [HGM] book: simulate book reply
13486         static char bookMove[MSG_SIZ]; // a bit generous?
13487
13488         programStats.nodes = programStats.depth = programStats.time =
13489         programStats.score = programStats.got_only_move = 0;
13490         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13491
13492         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13493         strcat(bookMove, bookHit);
13494         HandleMachineMove(bookMove, &first);
13495     }
13496 }
13497
13498
13499 void
13500 DisplayTwoMachinesTitle ()
13501 {
13502     char buf[MSG_SIZ];
13503     if (appData.matchGames > 0) {
13504         if(appData.tourneyFile[0]) {
13505           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13506                    gameInfo.white, _("vs."), gameInfo.black,
13507                    nextGame+1, appData.matchGames+1,
13508                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13509         } else 
13510         if (first.twoMachinesColor[0] == 'w') {
13511           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13512                    gameInfo.white, _("vs."),  gameInfo.black,
13513                    first.matchWins, second.matchWins,
13514                    matchGame - 1 - (first.matchWins + second.matchWins));
13515         } else {
13516           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13517                    gameInfo.white, _("vs."), gameInfo.black,
13518                    second.matchWins, first.matchWins,
13519                    matchGame - 1 - (first.matchWins + second.matchWins));
13520         }
13521     } else {
13522       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13523     }
13524     DisplayTitle(buf);
13525 }
13526
13527 void
13528 SettingsMenuIfReady ()
13529 {
13530   if (second.lastPing != second.lastPong) {
13531     DisplayMessage("", _("Waiting for second chess program"));
13532     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13533     return;
13534   }
13535   ThawUI();
13536   DisplayMessage("", "");
13537   SettingsPopUp(&second);
13538 }
13539
13540 int
13541 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13542 {
13543     char buf[MSG_SIZ];
13544     if (cps->pr == NoProc) {
13545         StartChessProgram(cps);
13546         if (cps->protocolVersion == 1) {
13547           retry();
13548         } else {
13549           /* kludge: allow timeout for initial "feature" command */
13550           FreezeUI();
13551           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13552           DisplayMessage("", buf);
13553           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13554         }
13555         return 1;
13556     }
13557     return 0;
13558 }
13559
13560 void
13561 TwoMachinesEvent P((void))
13562 {
13563     int i;
13564     char buf[MSG_SIZ];
13565     ChessProgramState *onmove;
13566     char *bookHit = NULL;
13567     static int stalling = 0;
13568     TimeMark now;
13569     long wait;
13570
13571     if (appData.noChessProgram) return;
13572
13573     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13574         DisplayError("second engine does not play this", 0);
13575         return;
13576     }
13577
13578     switch (gameMode) {
13579       case TwoMachinesPlay:
13580         return;
13581       case MachinePlaysWhite:
13582       case MachinePlaysBlack:
13583         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13584             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13585             return;
13586         }
13587         /* fall through */
13588       case BeginningOfGame:
13589       case PlayFromGameFile:
13590       case EndOfGame:
13591         EditGameEvent();
13592         if (gameMode != EditGame) return;
13593         break;
13594       case EditPosition:
13595         EditPositionDone(TRUE);
13596         break;
13597       case AnalyzeMode:
13598       case AnalyzeFile:
13599         ExitAnalyzeMode();
13600         break;
13601       case EditGame:
13602       default:
13603         break;
13604     }
13605
13606 //    forwardMostMove = currentMove;
13607     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13608
13609     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13610
13611     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13612     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13613       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13614       return;
13615     }
13616     if(!stalling) {
13617       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13618       SendToProgram("force\n", &second);
13619       stalling = 1;
13620       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13621       return;
13622     }
13623     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13624     if(appData.matchPause>10000 || appData.matchPause<10)
13625                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13626     wait = SubtractTimeMarks(&now, &pauseStart);
13627     if(wait < appData.matchPause) {
13628         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13629         return;
13630     }
13631     // we are now committed to starting the game
13632     stalling = 0;
13633     DisplayMessage("", "");
13634     if (startedFromSetupPosition) {
13635         SendBoard(&second, backwardMostMove);
13636     if (appData.debugMode) {
13637         fprintf(debugFP, "Two Machines\n");
13638     }
13639     }
13640     for (i = backwardMostMove; i < forwardMostMove; i++) {
13641         SendMoveToProgram(i, &second);
13642     }
13643
13644     gameMode = TwoMachinesPlay;
13645     pausing = FALSE;
13646     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13647     SetGameInfo();
13648     DisplayTwoMachinesTitle();
13649     firstMove = TRUE;
13650     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13651         onmove = &first;
13652     } else {
13653         onmove = &second;
13654     }
13655     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13656     SendToProgram(first.computerString, &first);
13657     if (first.sendName) {
13658       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13659       SendToProgram(buf, &first);
13660     }
13661     SendToProgram(second.computerString, &second);
13662     if (second.sendName) {
13663       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13664       SendToProgram(buf, &second);
13665     }
13666
13667     ResetClocks();
13668     if (!first.sendTime || !second.sendTime) {
13669         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13670         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13671     }
13672     if (onmove->sendTime) {
13673       if (onmove->useColors) {
13674         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13675       }
13676       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13677     }
13678     if (onmove->useColors) {
13679       SendToProgram(onmove->twoMachinesColor, onmove);
13680     }
13681     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13682 //    SendToProgram("go\n", onmove);
13683     onmove->maybeThinking = TRUE;
13684     SetMachineThinkingEnables();
13685
13686     StartClocks();
13687
13688     if(bookHit) { // [HGM] book: simulate book reply
13689         static char bookMove[MSG_SIZ]; // a bit generous?
13690
13691         programStats.nodes = programStats.depth = programStats.time =
13692         programStats.score = programStats.got_only_move = 0;
13693         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13694
13695         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13696         strcat(bookMove, bookHit);
13697         savedMessage = bookMove; // args for deferred call
13698         savedState = onmove;
13699         ScheduleDelayedEvent(DeferredBookMove, 1);
13700     }
13701 }
13702
13703 void
13704 TrainingEvent ()
13705 {
13706     if (gameMode == Training) {
13707       SetTrainingModeOff();
13708       gameMode = PlayFromGameFile;
13709       DisplayMessage("", _("Training mode off"));
13710     } else {
13711       gameMode = Training;
13712       animateTraining = appData.animate;
13713
13714       /* make sure we are not already at the end of the game */
13715       if (currentMove < forwardMostMove) {
13716         SetTrainingModeOn();
13717         DisplayMessage("", _("Training mode on"));
13718       } else {
13719         gameMode = PlayFromGameFile;
13720         DisplayError(_("Already at end of game"), 0);
13721       }
13722     }
13723     ModeHighlight();
13724 }
13725
13726 void
13727 IcsClientEvent ()
13728 {
13729     if (!appData.icsActive) return;
13730     switch (gameMode) {
13731       case IcsPlayingWhite:
13732       case IcsPlayingBlack:
13733       case IcsObserving:
13734       case IcsIdle:
13735       case BeginningOfGame:
13736       case IcsExamining:
13737         return;
13738
13739       case EditGame:
13740         break;
13741
13742       case EditPosition:
13743         EditPositionDone(TRUE);
13744         break;
13745
13746       case AnalyzeMode:
13747       case AnalyzeFile:
13748         ExitAnalyzeMode();
13749         break;
13750
13751       default:
13752         EditGameEvent();
13753         break;
13754     }
13755
13756     gameMode = IcsIdle;
13757     ModeHighlight();
13758     return;
13759 }
13760
13761 void
13762 EditGameEvent ()
13763 {
13764     int i;
13765
13766     switch (gameMode) {
13767       case Training:
13768         SetTrainingModeOff();
13769         break;
13770       case MachinePlaysWhite:
13771       case MachinePlaysBlack:
13772       case BeginningOfGame:
13773         SendToProgram("force\n", &first);
13774         SetUserThinkingEnables();
13775         break;
13776       case PlayFromGameFile:
13777         (void) StopLoadGameTimer();
13778         if (gameFileFP != NULL) {
13779             gameFileFP = NULL;
13780         }
13781         break;
13782       case EditPosition:
13783         EditPositionDone(TRUE);
13784         break;
13785       case AnalyzeMode:
13786       case AnalyzeFile:
13787         ExitAnalyzeMode();
13788         SendToProgram("force\n", &first);
13789         break;
13790       case TwoMachinesPlay:
13791         GameEnds(EndOfFile, NULL, GE_PLAYER);
13792         ResurrectChessProgram();
13793         SetUserThinkingEnables();
13794         break;
13795       case EndOfGame:
13796         ResurrectChessProgram();
13797         break;
13798       case IcsPlayingBlack:
13799       case IcsPlayingWhite:
13800         DisplayError(_("Warning: You are still playing a game"), 0);
13801         break;
13802       case IcsObserving:
13803         DisplayError(_("Warning: You are still observing a game"), 0);
13804         break;
13805       case IcsExamining:
13806         DisplayError(_("Warning: You are still examining a game"), 0);
13807         break;
13808       case IcsIdle:
13809         break;
13810       case EditGame:
13811       default:
13812         return;
13813     }
13814
13815     pausing = FALSE;
13816     StopClocks();
13817     first.offeredDraw = second.offeredDraw = 0;
13818
13819     if (gameMode == PlayFromGameFile) {
13820         whiteTimeRemaining = timeRemaining[0][currentMove];
13821         blackTimeRemaining = timeRemaining[1][currentMove];
13822         DisplayTitle("");
13823     }
13824
13825     if (gameMode == MachinePlaysWhite ||
13826         gameMode == MachinePlaysBlack ||
13827         gameMode == TwoMachinesPlay ||
13828         gameMode == EndOfGame) {
13829         i = forwardMostMove;
13830         while (i > currentMove) {
13831             SendToProgram("undo\n", &first);
13832             i--;
13833         }
13834         if(!adjustedClock) {
13835         whiteTimeRemaining = timeRemaining[0][currentMove];
13836         blackTimeRemaining = timeRemaining[1][currentMove];
13837         DisplayBothClocks();
13838         }
13839         if (whiteFlag || blackFlag) {
13840             whiteFlag = blackFlag = 0;
13841         }
13842         DisplayTitle("");
13843     }
13844
13845     gameMode = EditGame;
13846     ModeHighlight();
13847     SetGameInfo();
13848 }
13849
13850
13851 void
13852 EditPositionEvent ()
13853 {
13854     if (gameMode == EditPosition) {
13855         EditGameEvent();
13856         return;
13857     }
13858
13859     EditGameEvent();
13860     if (gameMode != EditGame) return;
13861
13862     gameMode = EditPosition;
13863     ModeHighlight();
13864     SetGameInfo();
13865     if (currentMove > 0)
13866       CopyBoard(boards[0], boards[currentMove]);
13867
13868     blackPlaysFirst = !WhiteOnMove(currentMove);
13869     ResetClocks();
13870     currentMove = forwardMostMove = backwardMostMove = 0;
13871     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13872     DisplayMove(-1);
13873     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13874 }
13875
13876 void
13877 ExitAnalyzeMode ()
13878 {
13879     /* [DM] icsEngineAnalyze - possible call from other functions */
13880     if (appData.icsEngineAnalyze) {
13881         appData.icsEngineAnalyze = FALSE;
13882
13883         DisplayMessage("",_("Close ICS engine analyze..."));
13884     }
13885     if (first.analysisSupport && first.analyzing) {
13886       SendToProgram("exit\n", &first);
13887       first.analyzing = FALSE;
13888     }
13889     thinkOutput[0] = NULLCHAR;
13890 }
13891
13892 void
13893 EditPositionDone (Boolean fakeRights)
13894 {
13895     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13896
13897     startedFromSetupPosition = TRUE;
13898     InitChessProgram(&first, FALSE);
13899     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13900       boards[0][EP_STATUS] = EP_NONE;
13901       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13902     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13903         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13904         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13905       } else boards[0][CASTLING][2] = NoRights;
13906     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13907         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13908         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13909       } else boards[0][CASTLING][5] = NoRights;
13910     }
13911     SendToProgram("force\n", &first);
13912     if (blackPlaysFirst) {
13913         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13914         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13915         currentMove = forwardMostMove = backwardMostMove = 1;
13916         CopyBoard(boards[1], boards[0]);
13917     } else {
13918         currentMove = forwardMostMove = backwardMostMove = 0;
13919     }
13920     SendBoard(&first, forwardMostMove);
13921     if (appData.debugMode) {
13922         fprintf(debugFP, "EditPosDone\n");
13923     }
13924     DisplayTitle("");
13925     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13926     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13927     gameMode = EditGame;
13928     ModeHighlight();
13929     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13930     ClearHighlights(); /* [AS] */
13931 }
13932
13933 /* Pause for `ms' milliseconds */
13934 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13935 void
13936 TimeDelay (long ms)
13937 {
13938     TimeMark m1, m2;
13939
13940     GetTimeMark(&m1);
13941     do {
13942         GetTimeMark(&m2);
13943     } while (SubtractTimeMarks(&m2, &m1) < ms);
13944 }
13945
13946 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13947 void
13948 SendMultiLineToICS (char *buf)
13949 {
13950     char temp[MSG_SIZ+1], *p;
13951     int len;
13952
13953     len = strlen(buf);
13954     if (len > MSG_SIZ)
13955       len = MSG_SIZ;
13956
13957     strncpy(temp, buf, len);
13958     temp[len] = 0;
13959
13960     p = temp;
13961     while (*p) {
13962         if (*p == '\n' || *p == '\r')
13963           *p = ' ';
13964         ++p;
13965     }
13966
13967     strcat(temp, "\n");
13968     SendToICS(temp);
13969     SendToPlayer(temp, strlen(temp));
13970 }
13971
13972 void
13973 SetWhiteToPlayEvent ()
13974 {
13975     if (gameMode == EditPosition) {
13976         blackPlaysFirst = FALSE;
13977         DisplayBothClocks();    /* works because currentMove is 0 */
13978     } else if (gameMode == IcsExamining) {
13979         SendToICS(ics_prefix);
13980         SendToICS("tomove white\n");
13981     }
13982 }
13983
13984 void
13985 SetBlackToPlayEvent ()
13986 {
13987     if (gameMode == EditPosition) {
13988         blackPlaysFirst = TRUE;
13989         currentMove = 1;        /* kludge */
13990         DisplayBothClocks();
13991         currentMove = 0;
13992     } else if (gameMode == IcsExamining) {
13993         SendToICS(ics_prefix);
13994         SendToICS("tomove black\n");
13995     }
13996 }
13997
13998 void
13999 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14000 {
14001     char buf[MSG_SIZ];
14002     ChessSquare piece = boards[0][y][x];
14003
14004     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14005
14006     switch (selection) {
14007       case ClearBoard:
14008         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14009             SendToICS(ics_prefix);
14010             SendToICS("bsetup clear\n");
14011         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14012             SendToICS(ics_prefix);
14013             SendToICS("clearboard\n");
14014         } else {
14015             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14016                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14017                 for (y = 0; y < BOARD_HEIGHT; y++) {
14018                     if (gameMode == IcsExamining) {
14019                         if (boards[currentMove][y][x] != EmptySquare) {
14020                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14021                                     AAA + x, ONE + y);
14022                             SendToICS(buf);
14023                         }
14024                     } else {
14025                         boards[0][y][x] = p;
14026                     }
14027                 }
14028             }
14029         }
14030         if (gameMode == EditPosition) {
14031             DrawPosition(FALSE, boards[0]);
14032         }
14033         break;
14034
14035       case WhitePlay:
14036         SetWhiteToPlayEvent();
14037         break;
14038
14039       case BlackPlay:
14040         SetBlackToPlayEvent();
14041         break;
14042
14043       case EmptySquare:
14044         if (gameMode == IcsExamining) {
14045             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14046             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14047             SendToICS(buf);
14048         } else {
14049             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14050                 if(x == BOARD_LEFT-2) {
14051                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14052                     boards[0][y][1] = 0;
14053                 } else
14054                 if(x == BOARD_RGHT+1) {
14055                     if(y >= gameInfo.holdingsSize) break;
14056                     boards[0][y][BOARD_WIDTH-2] = 0;
14057                 } else break;
14058             }
14059             boards[0][y][x] = EmptySquare;
14060             DrawPosition(FALSE, boards[0]);
14061         }
14062         break;
14063
14064       case PromotePiece:
14065         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14066            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14067             selection = (ChessSquare) (PROMOTED piece);
14068         } else if(piece == EmptySquare) selection = WhiteSilver;
14069         else selection = (ChessSquare)((int)piece - 1);
14070         goto defaultlabel;
14071
14072       case DemotePiece:
14073         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14074            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14075             selection = (ChessSquare) (DEMOTED piece);
14076         } else if(piece == EmptySquare) selection = BlackSilver;
14077         else selection = (ChessSquare)((int)piece + 1);
14078         goto defaultlabel;
14079
14080       case WhiteQueen:
14081       case BlackQueen:
14082         if(gameInfo.variant == VariantShatranj ||
14083            gameInfo.variant == VariantXiangqi  ||
14084            gameInfo.variant == VariantCourier  ||
14085            gameInfo.variant == VariantMakruk     )
14086             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14087         goto defaultlabel;
14088
14089       case WhiteKing:
14090       case BlackKing:
14091         if(gameInfo.variant == VariantXiangqi)
14092             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14093         if(gameInfo.variant == VariantKnightmate)
14094             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14095       default:
14096         defaultlabel:
14097         if (gameMode == IcsExamining) {
14098             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14099             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14100                      PieceToChar(selection), AAA + x, ONE + y);
14101             SendToICS(buf);
14102         } else {
14103             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14104                 int n;
14105                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14106                     n = PieceToNumber(selection - BlackPawn);
14107                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14108                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14109                     boards[0][BOARD_HEIGHT-1-n][1]++;
14110                 } else
14111                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14112                     n = PieceToNumber(selection);
14113                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14114                     boards[0][n][BOARD_WIDTH-1] = selection;
14115                     boards[0][n][BOARD_WIDTH-2]++;
14116                 }
14117             } else
14118             boards[0][y][x] = selection;
14119             DrawPosition(TRUE, boards[0]);
14120             ClearHighlights();
14121             fromX = fromY = -1;
14122         }
14123         break;
14124     }
14125 }
14126
14127
14128 void
14129 DropMenuEvent (ChessSquare selection, int x, int y)
14130 {
14131     ChessMove moveType;
14132
14133     switch (gameMode) {
14134       case IcsPlayingWhite:
14135       case MachinePlaysBlack:
14136         if (!WhiteOnMove(currentMove)) {
14137             DisplayMoveError(_("It is Black's turn"));
14138             return;
14139         }
14140         moveType = WhiteDrop;
14141         break;
14142       case IcsPlayingBlack:
14143       case MachinePlaysWhite:
14144         if (WhiteOnMove(currentMove)) {
14145             DisplayMoveError(_("It is White's turn"));
14146             return;
14147         }
14148         moveType = BlackDrop;
14149         break;
14150       case EditGame:
14151         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14152         break;
14153       default:
14154         return;
14155     }
14156
14157     if (moveType == BlackDrop && selection < BlackPawn) {
14158       selection = (ChessSquare) ((int) selection
14159                                  + (int) BlackPawn - (int) WhitePawn);
14160     }
14161     if (boards[currentMove][y][x] != EmptySquare) {
14162         DisplayMoveError(_("That square is occupied"));
14163         return;
14164     }
14165
14166     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14167 }
14168
14169 void
14170 AcceptEvent ()
14171 {
14172     /* Accept a pending offer of any kind from opponent */
14173
14174     if (appData.icsActive) {
14175         SendToICS(ics_prefix);
14176         SendToICS("accept\n");
14177     } else if (cmailMsgLoaded) {
14178         if (currentMove == cmailOldMove &&
14179             commentList[cmailOldMove] != NULL &&
14180             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14181                    "Black offers a draw" : "White offers a draw")) {
14182             TruncateGame();
14183             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14184             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14185         } else {
14186             DisplayError(_("There is no pending offer on this move"), 0);
14187             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14188         }
14189     } else {
14190         /* Not used for offers from chess program */
14191     }
14192 }
14193
14194 void
14195 DeclineEvent ()
14196 {
14197     /* Decline a pending offer of any kind from opponent */
14198
14199     if (appData.icsActive) {
14200         SendToICS(ics_prefix);
14201         SendToICS("decline\n");
14202     } else if (cmailMsgLoaded) {
14203         if (currentMove == cmailOldMove &&
14204             commentList[cmailOldMove] != NULL &&
14205             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14206                    "Black offers a draw" : "White offers a draw")) {
14207 #ifdef NOTDEF
14208             AppendComment(cmailOldMove, "Draw declined", TRUE);
14209             DisplayComment(cmailOldMove - 1, "Draw declined");
14210 #endif /*NOTDEF*/
14211         } else {
14212             DisplayError(_("There is no pending offer on this move"), 0);
14213         }
14214     } else {
14215         /* Not used for offers from chess program */
14216     }
14217 }
14218
14219 void
14220 RematchEvent ()
14221 {
14222     /* Issue ICS rematch command */
14223     if (appData.icsActive) {
14224         SendToICS(ics_prefix);
14225         SendToICS("rematch\n");
14226     }
14227 }
14228
14229 void
14230 CallFlagEvent ()
14231 {
14232     /* Call your opponent's flag (claim a win on time) */
14233     if (appData.icsActive) {
14234         SendToICS(ics_prefix);
14235         SendToICS("flag\n");
14236     } else {
14237         switch (gameMode) {
14238           default:
14239             return;
14240           case MachinePlaysWhite:
14241             if (whiteFlag) {
14242                 if (blackFlag)
14243                   GameEnds(GameIsDrawn, "Both players ran out of time",
14244                            GE_PLAYER);
14245                 else
14246                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14247             } else {
14248                 DisplayError(_("Your opponent is not out of time"), 0);
14249             }
14250             break;
14251           case MachinePlaysBlack:
14252             if (blackFlag) {
14253                 if (whiteFlag)
14254                   GameEnds(GameIsDrawn, "Both players ran out of time",
14255                            GE_PLAYER);
14256                 else
14257                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14258             } else {
14259                 DisplayError(_("Your opponent is not out of time"), 0);
14260             }
14261             break;
14262         }
14263     }
14264 }
14265
14266 void
14267 ClockClick (int which)
14268 {       // [HGM] code moved to back-end from winboard.c
14269         if(which) { // black clock
14270           if (gameMode == EditPosition || gameMode == IcsExamining) {
14271             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14272             SetBlackToPlayEvent();
14273           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14274           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14275           } else if (shiftKey) {
14276             AdjustClock(which, -1);
14277           } else if (gameMode == IcsPlayingWhite ||
14278                      gameMode == MachinePlaysBlack) {
14279             CallFlagEvent();
14280           }
14281         } else { // white clock
14282           if (gameMode == EditPosition || gameMode == IcsExamining) {
14283             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14284             SetWhiteToPlayEvent();
14285           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14286           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14287           } else if (shiftKey) {
14288             AdjustClock(which, -1);
14289           } else if (gameMode == IcsPlayingBlack ||
14290                    gameMode == MachinePlaysWhite) {
14291             CallFlagEvent();
14292           }
14293         }
14294 }
14295
14296 void
14297 DrawEvent ()
14298 {
14299     /* Offer draw or accept pending draw offer from opponent */
14300
14301     if (appData.icsActive) {
14302         /* Note: tournament rules require draw offers to be
14303            made after you make your move but before you punch
14304            your clock.  Currently ICS doesn't let you do that;
14305            instead, you immediately punch your clock after making
14306            a move, but you can offer a draw at any time. */
14307
14308         SendToICS(ics_prefix);
14309         SendToICS("draw\n");
14310         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14311     } else if (cmailMsgLoaded) {
14312         if (currentMove == cmailOldMove &&
14313             commentList[cmailOldMove] != NULL &&
14314             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14315                    "Black offers a draw" : "White offers a draw")) {
14316             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14317             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14318         } else if (currentMove == cmailOldMove + 1) {
14319             char *offer = WhiteOnMove(cmailOldMove) ?
14320               "White offers a draw" : "Black offers a draw";
14321             AppendComment(currentMove, offer, TRUE);
14322             DisplayComment(currentMove - 1, offer);
14323             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14324         } else {
14325             DisplayError(_("You must make your move before offering a draw"), 0);
14326             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14327         }
14328     } else if (first.offeredDraw) {
14329         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14330     } else {
14331         if (first.sendDrawOffers) {
14332             SendToProgram("draw\n", &first);
14333             userOfferedDraw = TRUE;
14334         }
14335     }
14336 }
14337
14338 void
14339 AdjournEvent ()
14340 {
14341     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14342
14343     if (appData.icsActive) {
14344         SendToICS(ics_prefix);
14345         SendToICS("adjourn\n");
14346     } else {
14347         /* Currently GNU Chess doesn't offer or accept Adjourns */
14348     }
14349 }
14350
14351
14352 void
14353 AbortEvent ()
14354 {
14355     /* Offer Abort or accept pending Abort offer from opponent */
14356
14357     if (appData.icsActive) {
14358         SendToICS(ics_prefix);
14359         SendToICS("abort\n");
14360     } else {
14361         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14362     }
14363 }
14364
14365 void
14366 ResignEvent ()
14367 {
14368     /* Resign.  You can do this even if it's not your turn. */
14369
14370     if (appData.icsActive) {
14371         SendToICS(ics_prefix);
14372         SendToICS("resign\n");
14373     } else {
14374         switch (gameMode) {
14375           case MachinePlaysWhite:
14376             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14377             break;
14378           case MachinePlaysBlack:
14379             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14380             break;
14381           case EditGame:
14382             if (cmailMsgLoaded) {
14383                 TruncateGame();
14384                 if (WhiteOnMove(cmailOldMove)) {
14385                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14386                 } else {
14387                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14388                 }
14389                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14390             }
14391             break;
14392           default:
14393             break;
14394         }
14395     }
14396 }
14397
14398
14399 void
14400 StopObservingEvent ()
14401 {
14402     /* Stop observing current games */
14403     SendToICS(ics_prefix);
14404     SendToICS("unobserve\n");
14405 }
14406
14407 void
14408 StopExaminingEvent ()
14409 {
14410     /* Stop observing current game */
14411     SendToICS(ics_prefix);
14412     SendToICS("unexamine\n");
14413 }
14414
14415 void
14416 ForwardInner (int target)
14417 {
14418     int limit; int oldSeekGraphUp = seekGraphUp;
14419
14420     if (appData.debugMode)
14421         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14422                 target, currentMove, forwardMostMove);
14423
14424     if (gameMode == EditPosition)
14425       return;
14426
14427     seekGraphUp = FALSE;
14428     MarkTargetSquares(1);
14429
14430     if (gameMode == PlayFromGameFile && !pausing)
14431       PauseEvent();
14432
14433     if (gameMode == IcsExamining && pausing)
14434       limit = pauseExamForwardMostMove;
14435     else
14436       limit = forwardMostMove;
14437
14438     if (target > limit) target = limit;
14439
14440     if (target > 0 && moveList[target - 1][0]) {
14441         int fromX, fromY, toX, toY;
14442         toX = moveList[target - 1][2] - AAA;
14443         toY = moveList[target - 1][3] - ONE;
14444         if (moveList[target - 1][1] == '@') {
14445             if (appData.highlightLastMove) {
14446                 SetHighlights(-1, -1, toX, toY);
14447             }
14448         } else {
14449             fromX = moveList[target - 1][0] - AAA;
14450             fromY = moveList[target - 1][1] - ONE;
14451             if (target == currentMove + 1) {
14452                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14453             }
14454             if (appData.highlightLastMove) {
14455                 SetHighlights(fromX, fromY, toX, toY);
14456             }
14457         }
14458     }
14459     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14460         gameMode == Training || gameMode == PlayFromGameFile ||
14461         gameMode == AnalyzeFile) {
14462         while (currentMove < target) {
14463             SendMoveToProgram(currentMove++, &first);
14464         }
14465     } else {
14466         currentMove = target;
14467     }
14468
14469     if (gameMode == EditGame || gameMode == EndOfGame) {
14470         whiteTimeRemaining = timeRemaining[0][currentMove];
14471         blackTimeRemaining = timeRemaining[1][currentMove];
14472     }
14473     DisplayBothClocks();
14474     DisplayMove(currentMove - 1);
14475     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14476     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14477     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14478         DisplayComment(currentMove - 1, commentList[currentMove]);
14479     }
14480     ClearMap(); // [HGM] exclude: invalidate map
14481 }
14482
14483
14484 void
14485 ForwardEvent ()
14486 {
14487     if (gameMode == IcsExamining && !pausing) {
14488         SendToICS(ics_prefix);
14489         SendToICS("forward\n");
14490     } else {
14491         ForwardInner(currentMove + 1);
14492     }
14493 }
14494
14495 void
14496 ToEndEvent ()
14497 {
14498     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14499         /* to optimze, we temporarily turn off analysis mode while we feed
14500          * the remaining moves to the engine. Otherwise we get analysis output
14501          * after each move.
14502          */
14503         if (first.analysisSupport) {
14504           SendToProgram("exit\nforce\n", &first);
14505           first.analyzing = FALSE;
14506         }
14507     }
14508
14509     if (gameMode == IcsExamining && !pausing) {
14510         SendToICS(ics_prefix);
14511         SendToICS("forward 999999\n");
14512     } else {
14513         ForwardInner(forwardMostMove);
14514     }
14515
14516     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14517         /* we have fed all the moves, so reactivate analysis mode */
14518         SendToProgram("analyze\n", &first);
14519         first.analyzing = TRUE;
14520         /*first.maybeThinking = TRUE;*/
14521         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14522     }
14523 }
14524
14525 void
14526 BackwardInner (int target)
14527 {
14528     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14529
14530     if (appData.debugMode)
14531         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14532                 target, currentMove, forwardMostMove);
14533
14534     if (gameMode == EditPosition) return;
14535     seekGraphUp = FALSE;
14536     MarkTargetSquares(1);
14537     if (currentMove <= backwardMostMove) {
14538         ClearHighlights();
14539         DrawPosition(full_redraw, boards[currentMove]);
14540         return;
14541     }
14542     if (gameMode == PlayFromGameFile && !pausing)
14543       PauseEvent();
14544
14545     if (moveList[target][0]) {
14546         int fromX, fromY, toX, toY;
14547         toX = moveList[target][2] - AAA;
14548         toY = moveList[target][3] - ONE;
14549         if (moveList[target][1] == '@') {
14550             if (appData.highlightLastMove) {
14551                 SetHighlights(-1, -1, toX, toY);
14552             }
14553         } else {
14554             fromX = moveList[target][0] - AAA;
14555             fromY = moveList[target][1] - ONE;
14556             if (target == currentMove - 1) {
14557                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14558             }
14559             if (appData.highlightLastMove) {
14560                 SetHighlights(fromX, fromY, toX, toY);
14561             }
14562         }
14563     }
14564     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14565         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14566         while (currentMove > target) {
14567             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14568                 // null move cannot be undone. Reload program with move history before it.
14569                 int i;
14570                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14571                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14572                 }
14573                 SendBoard(&first, i); 
14574                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14575                 break;
14576             }
14577             SendToProgram("undo\n", &first);
14578             currentMove--;
14579         }
14580     } else {
14581         currentMove = target;
14582     }
14583
14584     if (gameMode == EditGame || gameMode == EndOfGame) {
14585         whiteTimeRemaining = timeRemaining[0][currentMove];
14586         blackTimeRemaining = timeRemaining[1][currentMove];
14587     }
14588     DisplayBothClocks();
14589     DisplayMove(currentMove - 1);
14590     DrawPosition(full_redraw, boards[currentMove]);
14591     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14592     // [HGM] PV info: routine tests if comment empty
14593     DisplayComment(currentMove - 1, commentList[currentMove]);
14594     ClearMap(); // [HGM] exclude: invalidate map
14595 }
14596
14597 void
14598 BackwardEvent ()
14599 {
14600     if (gameMode == IcsExamining && !pausing) {
14601         SendToICS(ics_prefix);
14602         SendToICS("backward\n");
14603     } else {
14604         BackwardInner(currentMove - 1);
14605     }
14606 }
14607
14608 void
14609 ToStartEvent ()
14610 {
14611     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14612         /* to optimize, we temporarily turn off analysis mode while we undo
14613          * all the moves. Otherwise we get analysis output after each undo.
14614          */
14615         if (first.analysisSupport) {
14616           SendToProgram("exit\nforce\n", &first);
14617           first.analyzing = FALSE;
14618         }
14619     }
14620
14621     if (gameMode == IcsExamining && !pausing) {
14622         SendToICS(ics_prefix);
14623         SendToICS("backward 999999\n");
14624     } else {
14625         BackwardInner(backwardMostMove);
14626     }
14627
14628     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14629         /* we have fed all the moves, so reactivate analysis mode */
14630         SendToProgram("analyze\n", &first);
14631         first.analyzing = TRUE;
14632         /*first.maybeThinking = TRUE;*/
14633         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14634     }
14635 }
14636
14637 void
14638 ToNrEvent (int to)
14639 {
14640   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14641   if (to >= forwardMostMove) to = forwardMostMove;
14642   if (to <= backwardMostMove) to = backwardMostMove;
14643   if (to < currentMove) {
14644     BackwardInner(to);
14645   } else {
14646     ForwardInner(to);
14647   }
14648 }
14649
14650 void
14651 RevertEvent (Boolean annotate)
14652 {
14653     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14654         return;
14655     }
14656     if (gameMode != IcsExamining) {
14657         DisplayError(_("You are not examining a game"), 0);
14658         return;
14659     }
14660     if (pausing) {
14661         DisplayError(_("You can't revert while pausing"), 0);
14662         return;
14663     }
14664     SendToICS(ics_prefix);
14665     SendToICS("revert\n");
14666 }
14667
14668 void
14669 RetractMoveEvent ()
14670 {
14671     switch (gameMode) {
14672       case MachinePlaysWhite:
14673       case MachinePlaysBlack:
14674         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14675             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14676             return;
14677         }
14678         if (forwardMostMove < 2) return;
14679         currentMove = forwardMostMove = forwardMostMove - 2;
14680         whiteTimeRemaining = timeRemaining[0][currentMove];
14681         blackTimeRemaining = timeRemaining[1][currentMove];
14682         DisplayBothClocks();
14683         DisplayMove(currentMove - 1);
14684         ClearHighlights();/*!! could figure this out*/
14685         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14686         SendToProgram("remove\n", &first);
14687         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14688         break;
14689
14690       case BeginningOfGame:
14691       default:
14692         break;
14693
14694       case IcsPlayingWhite:
14695       case IcsPlayingBlack:
14696         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14697             SendToICS(ics_prefix);
14698             SendToICS("takeback 2\n");
14699         } else {
14700             SendToICS(ics_prefix);
14701             SendToICS("takeback 1\n");
14702         }
14703         break;
14704     }
14705 }
14706
14707 void
14708 MoveNowEvent ()
14709 {
14710     ChessProgramState *cps;
14711
14712     switch (gameMode) {
14713       case MachinePlaysWhite:
14714         if (!WhiteOnMove(forwardMostMove)) {
14715             DisplayError(_("It is your turn"), 0);
14716             return;
14717         }
14718         cps = &first;
14719         break;
14720       case MachinePlaysBlack:
14721         if (WhiteOnMove(forwardMostMove)) {
14722             DisplayError(_("It is your turn"), 0);
14723             return;
14724         }
14725         cps = &first;
14726         break;
14727       case TwoMachinesPlay:
14728         if (WhiteOnMove(forwardMostMove) ==
14729             (first.twoMachinesColor[0] == 'w')) {
14730             cps = &first;
14731         } else {
14732             cps = &second;
14733         }
14734         break;
14735       case BeginningOfGame:
14736       default:
14737         return;
14738     }
14739     SendToProgram("?\n", cps);
14740 }
14741
14742 void
14743 TruncateGameEvent ()
14744 {
14745     EditGameEvent();
14746     if (gameMode != EditGame) return;
14747     TruncateGame();
14748 }
14749
14750 void
14751 TruncateGame ()
14752 {
14753     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14754     if (forwardMostMove > currentMove) {
14755         if (gameInfo.resultDetails != NULL) {
14756             free(gameInfo.resultDetails);
14757             gameInfo.resultDetails = NULL;
14758             gameInfo.result = GameUnfinished;
14759         }
14760         forwardMostMove = currentMove;
14761         HistorySet(parseList, backwardMostMove, forwardMostMove,
14762                    currentMove-1);
14763     }
14764 }
14765
14766 void
14767 HintEvent ()
14768 {
14769     if (appData.noChessProgram) return;
14770     switch (gameMode) {
14771       case MachinePlaysWhite:
14772         if (WhiteOnMove(forwardMostMove)) {
14773             DisplayError(_("Wait until your turn"), 0);
14774             return;
14775         }
14776         break;
14777       case BeginningOfGame:
14778       case MachinePlaysBlack:
14779         if (!WhiteOnMove(forwardMostMove)) {
14780             DisplayError(_("Wait until your turn"), 0);
14781             return;
14782         }
14783         break;
14784       default:
14785         DisplayError(_("No hint available"), 0);
14786         return;
14787     }
14788     SendToProgram("hint\n", &first);
14789     hintRequested = TRUE;
14790 }
14791
14792 void
14793 BookEvent ()
14794 {
14795     if (appData.noChessProgram) return;
14796     switch (gameMode) {
14797       case MachinePlaysWhite:
14798         if (WhiteOnMove(forwardMostMove)) {
14799             DisplayError(_("Wait until your turn"), 0);
14800             return;
14801         }
14802         break;
14803       case BeginningOfGame:
14804       case MachinePlaysBlack:
14805         if (!WhiteOnMove(forwardMostMove)) {
14806             DisplayError(_("Wait until your turn"), 0);
14807             return;
14808         }
14809         break;
14810       case EditPosition:
14811         EditPositionDone(TRUE);
14812         break;
14813       case TwoMachinesPlay:
14814         return;
14815       default:
14816         break;
14817     }
14818     SendToProgram("bk\n", &first);
14819     bookOutput[0] = NULLCHAR;
14820     bookRequested = TRUE;
14821 }
14822
14823 void
14824 AboutGameEvent ()
14825 {
14826     char *tags = PGNTags(&gameInfo);
14827     TagsPopUp(tags, CmailMsg());
14828     free(tags);
14829 }
14830
14831 /* end button procedures */
14832
14833 void
14834 PrintPosition (FILE *fp, int move)
14835 {
14836     int i, j;
14837
14838     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14839         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14840             char c = PieceToChar(boards[move][i][j]);
14841             fputc(c == 'x' ? '.' : c, fp);
14842             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14843         }
14844     }
14845     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14846       fprintf(fp, "white to play\n");
14847     else
14848       fprintf(fp, "black to play\n");
14849 }
14850
14851 void
14852 PrintOpponents (FILE *fp)
14853 {
14854     if (gameInfo.white != NULL) {
14855         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14856     } else {
14857         fprintf(fp, "\n");
14858     }
14859 }
14860
14861 /* Find last component of program's own name, using some heuristics */
14862 void
14863 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14864 {
14865     char *p, *q, c;
14866     int local = (strcmp(host, "localhost") == 0);
14867     while (!local && (p = strchr(prog, ';')) != NULL) {
14868         p++;
14869         while (*p == ' ') p++;
14870         prog = p;
14871     }
14872     if (*prog == '"' || *prog == '\'') {
14873         q = strchr(prog + 1, *prog);
14874     } else {
14875         q = strchr(prog, ' ');
14876     }
14877     if (q == NULL) q = prog + strlen(prog);
14878     p = q;
14879     while (p >= prog && *p != '/' && *p != '\\') p--;
14880     p++;
14881     if(p == prog && *p == '"') p++;
14882     c = *q; *q = 0;
14883     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14884     memcpy(buf, p, q - p);
14885     buf[q - p] = NULLCHAR;
14886     if (!local) {
14887         strcat(buf, "@");
14888         strcat(buf, host);
14889     }
14890 }
14891
14892 char *
14893 TimeControlTagValue ()
14894 {
14895     char buf[MSG_SIZ];
14896     if (!appData.clockMode) {
14897       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14898     } else if (movesPerSession > 0) {
14899       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14900     } else if (timeIncrement == 0) {
14901       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14902     } else {
14903       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14904     }
14905     return StrSave(buf);
14906 }
14907
14908 void
14909 SetGameInfo ()
14910 {
14911     /* This routine is used only for certain modes */
14912     VariantClass v = gameInfo.variant;
14913     ChessMove r = GameUnfinished;
14914     char *p = NULL;
14915
14916     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14917         r = gameInfo.result;
14918         p = gameInfo.resultDetails;
14919         gameInfo.resultDetails = NULL;
14920     }
14921     ClearGameInfo(&gameInfo);
14922     gameInfo.variant = v;
14923
14924     switch (gameMode) {
14925       case MachinePlaysWhite:
14926         gameInfo.event = StrSave( appData.pgnEventHeader );
14927         gameInfo.site = StrSave(HostName());
14928         gameInfo.date = PGNDate();
14929         gameInfo.round = StrSave("-");
14930         gameInfo.white = StrSave(first.tidy);
14931         gameInfo.black = StrSave(UserName());
14932         gameInfo.timeControl = TimeControlTagValue();
14933         break;
14934
14935       case MachinePlaysBlack:
14936         gameInfo.event = StrSave( appData.pgnEventHeader );
14937         gameInfo.site = StrSave(HostName());
14938         gameInfo.date = PGNDate();
14939         gameInfo.round = StrSave("-");
14940         gameInfo.white = StrSave(UserName());
14941         gameInfo.black = StrSave(first.tidy);
14942         gameInfo.timeControl = TimeControlTagValue();
14943         break;
14944
14945       case TwoMachinesPlay:
14946         gameInfo.event = StrSave( appData.pgnEventHeader );
14947         gameInfo.site = StrSave(HostName());
14948         gameInfo.date = PGNDate();
14949         if (roundNr > 0) {
14950             char buf[MSG_SIZ];
14951             snprintf(buf, MSG_SIZ, "%d", roundNr);
14952             gameInfo.round = StrSave(buf);
14953         } else {
14954             gameInfo.round = StrSave("-");
14955         }
14956         if (first.twoMachinesColor[0] == 'w') {
14957             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14958             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14959         } else {
14960             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14961             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14962         }
14963         gameInfo.timeControl = TimeControlTagValue();
14964         break;
14965
14966       case EditGame:
14967         gameInfo.event = StrSave("Edited game");
14968         gameInfo.site = StrSave(HostName());
14969         gameInfo.date = PGNDate();
14970         gameInfo.round = StrSave("-");
14971         gameInfo.white = StrSave("-");
14972         gameInfo.black = StrSave("-");
14973         gameInfo.result = r;
14974         gameInfo.resultDetails = p;
14975         break;
14976
14977       case EditPosition:
14978         gameInfo.event = StrSave("Edited position");
14979         gameInfo.site = StrSave(HostName());
14980         gameInfo.date = PGNDate();
14981         gameInfo.round = StrSave("-");
14982         gameInfo.white = StrSave("-");
14983         gameInfo.black = StrSave("-");
14984         break;
14985
14986       case IcsPlayingWhite:
14987       case IcsPlayingBlack:
14988       case IcsObserving:
14989       case IcsExamining:
14990         break;
14991
14992       case PlayFromGameFile:
14993         gameInfo.event = StrSave("Game from non-PGN file");
14994         gameInfo.site = StrSave(HostName());
14995         gameInfo.date = PGNDate();
14996         gameInfo.round = StrSave("-");
14997         gameInfo.white = StrSave("?");
14998         gameInfo.black = StrSave("?");
14999         break;
15000
15001       default:
15002         break;
15003     }
15004 }
15005
15006 void
15007 ReplaceComment (int index, char *text)
15008 {
15009     int len;
15010     char *p;
15011     float score;
15012
15013     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15014        pvInfoList[index-1].depth == len &&
15015        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15016        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15017     while (*text == '\n') text++;
15018     len = strlen(text);
15019     while (len > 0 && text[len - 1] == '\n') len--;
15020
15021     if (commentList[index] != NULL)
15022       free(commentList[index]);
15023
15024     if (len == 0) {
15025         commentList[index] = NULL;
15026         return;
15027     }
15028   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15029       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15030       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15031     commentList[index] = (char *) malloc(len + 2);
15032     strncpy(commentList[index], text, len);
15033     commentList[index][len] = '\n';
15034     commentList[index][len + 1] = NULLCHAR;
15035   } else {
15036     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15037     char *p;
15038     commentList[index] = (char *) malloc(len + 7);
15039     safeStrCpy(commentList[index], "{\n", 3);
15040     safeStrCpy(commentList[index]+2, text, len+1);
15041     commentList[index][len+2] = NULLCHAR;
15042     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15043     strcat(commentList[index], "\n}\n");
15044   }
15045 }
15046
15047 void
15048 CrushCRs (char *text)
15049 {
15050   char *p = text;
15051   char *q = text;
15052   char ch;
15053
15054   do {
15055     ch = *p++;
15056     if (ch == '\r') continue;
15057     *q++ = ch;
15058   } while (ch != '\0');
15059 }
15060
15061 void
15062 AppendComment (int index, char *text, Boolean addBraces)
15063 /* addBraces  tells if we should add {} */
15064 {
15065     int oldlen, len;
15066     char *old;
15067
15068 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15069     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15070
15071     CrushCRs(text);
15072     while (*text == '\n') text++;
15073     len = strlen(text);
15074     while (len > 0 && text[len - 1] == '\n') len--;
15075     text[len] = NULLCHAR;
15076
15077     if (len == 0) return;
15078
15079     if (commentList[index] != NULL) {
15080       Boolean addClosingBrace = addBraces;
15081         old = commentList[index];
15082         oldlen = strlen(old);
15083         while(commentList[index][oldlen-1] ==  '\n')
15084           commentList[index][--oldlen] = NULLCHAR;
15085         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15086         safeStrCpy(commentList[index], old, oldlen + len + 6);
15087         free(old);
15088         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15089         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15090           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15091           while (*text == '\n') { text++; len--; }
15092           commentList[index][--oldlen] = NULLCHAR;
15093       }
15094         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15095         else          strcat(commentList[index], "\n");
15096         strcat(commentList[index], text);
15097         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15098         else          strcat(commentList[index], "\n");
15099     } else {
15100         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15101         if(addBraces)
15102           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15103         else commentList[index][0] = NULLCHAR;
15104         strcat(commentList[index], text);
15105         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15106         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15107     }
15108 }
15109
15110 static char *
15111 FindStr (char * text, char * sub_text)
15112 {
15113     char * result = strstr( text, sub_text );
15114
15115     if( result != NULL ) {
15116         result += strlen( sub_text );
15117     }
15118
15119     return result;
15120 }
15121
15122 /* [AS] Try to extract PV info from PGN comment */
15123 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15124 char *
15125 GetInfoFromComment (int index, char * text)
15126 {
15127     char * sep = text, *p;
15128
15129     if( text != NULL && index > 0 ) {
15130         int score = 0;
15131         int depth = 0;
15132         int time = -1, sec = 0, deci;
15133         char * s_eval = FindStr( text, "[%eval " );
15134         char * s_emt = FindStr( text, "[%emt " );
15135
15136         if( s_eval != NULL || s_emt != NULL ) {
15137             /* New style */
15138             char delim;
15139
15140             if( s_eval != NULL ) {
15141                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15142                     return text;
15143                 }
15144
15145                 if( delim != ']' ) {
15146                     return text;
15147                 }
15148             }
15149
15150             if( s_emt != NULL ) {
15151             }
15152                 return text;
15153         }
15154         else {
15155             /* We expect something like: [+|-]nnn.nn/dd */
15156             int score_lo = 0;
15157
15158             if(*text != '{') return text; // [HGM] braces: must be normal comment
15159
15160             sep = strchr( text, '/' );
15161             if( sep == NULL || sep < (text+4) ) {
15162                 return text;
15163             }
15164
15165             p = text;
15166             if(p[1] == '(') { // comment starts with PV
15167                p = strchr(p, ')'); // locate end of PV
15168                if(p == NULL || sep < p+5) return text;
15169                // at this point we have something like "{(.*) +0.23/6 ..."
15170                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15171                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15172                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15173             }
15174             time = -1; sec = -1; deci = -1;
15175             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15176                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15177                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15178                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15179                 return text;
15180             }
15181
15182             if( score_lo < 0 || score_lo >= 100 ) {
15183                 return text;
15184             }
15185
15186             if(sec >= 0) time = 600*time + 10*sec; else
15187             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15188
15189             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15190
15191             /* [HGM] PV time: now locate end of PV info */
15192             while( *++sep >= '0' && *sep <= '9'); // strip depth
15193             if(time >= 0)
15194             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15195             if(sec >= 0)
15196             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15197             if(deci >= 0)
15198             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15199             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15200         }
15201
15202         if( depth <= 0 ) {
15203             return text;
15204         }
15205
15206         if( time < 0 ) {
15207             time = -1;
15208         }
15209
15210         pvInfoList[index-1].depth = depth;
15211         pvInfoList[index-1].score = score;
15212         pvInfoList[index-1].time  = 10*time; // centi-sec
15213         if(*sep == '}') *sep = 0; else *--sep = '{';
15214         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15215     }
15216     return sep;
15217 }
15218
15219 void
15220 SendToProgram (char *message, ChessProgramState *cps)
15221 {
15222     int count, outCount, error;
15223     char buf[MSG_SIZ];
15224
15225     if (cps->pr == NoProc) return;
15226     Attention(cps);
15227
15228     if (appData.debugMode) {
15229         TimeMark now;
15230         GetTimeMark(&now);
15231         fprintf(debugFP, "%ld >%-6s: %s",
15232                 SubtractTimeMarks(&now, &programStartTime),
15233                 cps->which, message);
15234         if(serverFP)
15235             fprintf(serverFP, "%ld >%-6s: %s",
15236                 SubtractTimeMarks(&now, &programStartTime),
15237                 cps->which, message), fflush(serverFP);
15238     }
15239
15240     count = strlen(message);
15241     outCount = OutputToProcess(cps->pr, message, count, &error);
15242     if (outCount < count && !exiting
15243                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15244       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15245       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15246         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15247             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15248                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15249                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15250                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15251             } else {
15252                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15253                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15254                 gameInfo.result = res;
15255             }
15256             gameInfo.resultDetails = StrSave(buf);
15257         }
15258         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15259         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15260     }
15261 }
15262
15263 void
15264 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15265 {
15266     char *end_str;
15267     char buf[MSG_SIZ];
15268     ChessProgramState *cps = (ChessProgramState *)closure;
15269
15270     if (isr != cps->isr) return; /* Killed intentionally */
15271     if (count <= 0) {
15272         if (count == 0) {
15273             RemoveInputSource(cps->isr);
15274             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15275                     _(cps->which), cps->program);
15276             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15277             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15278                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15279                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15280                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15281                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15282                 } else {
15283                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15284                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15285                     gameInfo.result = res;
15286                 }
15287                 gameInfo.resultDetails = StrSave(buf);
15288             }
15289             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15290             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15291         } else {
15292             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15293                     _(cps->which), cps->program);
15294             RemoveInputSource(cps->isr);
15295
15296             /* [AS] Program is misbehaving badly... kill it */
15297             if( count == -2 ) {
15298                 DestroyChildProcess( cps->pr, 9 );
15299                 cps->pr = NoProc;
15300             }
15301
15302             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15303         }
15304         return;
15305     }
15306
15307     if ((end_str = strchr(message, '\r')) != NULL)
15308       *end_str = NULLCHAR;
15309     if ((end_str = strchr(message, '\n')) != NULL)
15310       *end_str = NULLCHAR;
15311
15312     if (appData.debugMode) {
15313         TimeMark now; int print = 1;
15314         char *quote = ""; char c; int i;
15315
15316         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15317                 char start = message[0];
15318                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15319                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15320                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15321                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15322                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15323                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15324                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15325                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15326                    sscanf(message, "hint: %c", &c)!=1 && 
15327                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15328                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15329                     print = (appData.engineComments >= 2);
15330                 }
15331                 message[0] = start; // restore original message
15332         }
15333         if(print) {
15334                 GetTimeMark(&now);
15335                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15336                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15337                         quote,
15338                         message);
15339                 if(serverFP)
15340                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15341                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15342                         quote,
15343                         message), fflush(serverFP);
15344         }
15345     }
15346
15347     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15348     if (appData.icsEngineAnalyze) {
15349         if (strstr(message, "whisper") != NULL ||
15350              strstr(message, "kibitz") != NULL ||
15351             strstr(message, "tellics") != NULL) return;
15352     }
15353
15354     HandleMachineMove(message, cps);
15355 }
15356
15357
15358 void
15359 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15360 {
15361     char buf[MSG_SIZ];
15362     int seconds;
15363
15364     if( timeControl_2 > 0 ) {
15365         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15366             tc = timeControl_2;
15367         }
15368     }
15369     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15370     inc /= cps->timeOdds;
15371     st  /= cps->timeOdds;
15372
15373     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15374
15375     if (st > 0) {
15376       /* Set exact time per move, normally using st command */
15377       if (cps->stKludge) {
15378         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15379         seconds = st % 60;
15380         if (seconds == 0) {
15381           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15382         } else {
15383           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15384         }
15385       } else {
15386         snprintf(buf, MSG_SIZ, "st %d\n", st);
15387       }
15388     } else {
15389       /* Set conventional or incremental time control, using level command */
15390       if (seconds == 0) {
15391         /* Note old gnuchess bug -- minutes:seconds used to not work.
15392            Fixed in later versions, but still avoid :seconds
15393            when seconds is 0. */
15394         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15395       } else {
15396         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15397                  seconds, inc/1000.);
15398       }
15399     }
15400     SendToProgram(buf, cps);
15401
15402     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15403     /* Orthogonally, limit search to given depth */
15404     if (sd > 0) {
15405       if (cps->sdKludge) {
15406         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15407       } else {
15408         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15409       }
15410       SendToProgram(buf, cps);
15411     }
15412
15413     if(cps->nps >= 0) { /* [HGM] nps */
15414         if(cps->supportsNPS == FALSE)
15415           cps->nps = -1; // don't use if engine explicitly says not supported!
15416         else {
15417           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15418           SendToProgram(buf, cps);
15419         }
15420     }
15421 }
15422
15423 ChessProgramState *
15424 WhitePlayer ()
15425 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15426 {
15427     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15428        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15429         return &second;
15430     return &first;
15431 }
15432
15433 void
15434 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15435 {
15436     char message[MSG_SIZ];
15437     long time, otime;
15438
15439     /* Note: this routine must be called when the clocks are stopped
15440        or when they have *just* been set or switched; otherwise
15441        it will be off by the time since the current tick started.
15442     */
15443     if (machineWhite) {
15444         time = whiteTimeRemaining / 10;
15445         otime = blackTimeRemaining / 10;
15446     } else {
15447         time = blackTimeRemaining / 10;
15448         otime = whiteTimeRemaining / 10;
15449     }
15450     /* [HGM] translate opponent's time by time-odds factor */
15451     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15452
15453     if (time <= 0) time = 1;
15454     if (otime <= 0) otime = 1;
15455
15456     snprintf(message, MSG_SIZ, "time %ld\n", time);
15457     SendToProgram(message, cps);
15458
15459     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15460     SendToProgram(message, cps);
15461 }
15462
15463 int
15464 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15465 {
15466   char buf[MSG_SIZ];
15467   int len = strlen(name);
15468   int val;
15469
15470   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15471     (*p) += len + 1;
15472     sscanf(*p, "%d", &val);
15473     *loc = (val != 0);
15474     while (**p && **p != ' ')
15475       (*p)++;
15476     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15477     SendToProgram(buf, cps);
15478     return TRUE;
15479   }
15480   return FALSE;
15481 }
15482
15483 int
15484 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15485 {
15486   char buf[MSG_SIZ];
15487   int len = strlen(name);
15488   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15489     (*p) += len + 1;
15490     sscanf(*p, "%d", loc);
15491     while (**p && **p != ' ') (*p)++;
15492     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15493     SendToProgram(buf, cps);
15494     return TRUE;
15495   }
15496   return FALSE;
15497 }
15498
15499 int
15500 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15501 {
15502   char buf[MSG_SIZ];
15503   int len = strlen(name);
15504   if (strncmp((*p), name, len) == 0
15505       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15506     (*p) += len + 2;
15507     sscanf(*p, "%[^\"]", loc);
15508     while (**p && **p != '\"') (*p)++;
15509     if (**p == '\"') (*p)++;
15510     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15511     SendToProgram(buf, cps);
15512     return TRUE;
15513   }
15514   return FALSE;
15515 }
15516
15517 int
15518 ParseOption (Option *opt, ChessProgramState *cps)
15519 // [HGM] options: process the string that defines an engine option, and determine
15520 // name, type, default value, and allowed value range
15521 {
15522         char *p, *q, buf[MSG_SIZ];
15523         int n, min = (-1)<<31, max = 1<<31, def;
15524
15525         if(p = strstr(opt->name, " -spin ")) {
15526             if((n = sscanf(p, " -spin %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;
15534         } else if((p = strstr(opt->name, " -slider "))) {
15535             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15536             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15537             if(max < min) max = min; // enforce consistency
15538             if(def < min) def = min;
15539             if(def > max) def = max;
15540             opt->value = def;
15541             opt->min = min;
15542             opt->max = max;
15543             opt->type = Spin; // Slider;
15544         } else if((p = strstr(opt->name, " -string "))) {
15545             opt->textValue = p+9;
15546             opt->type = TextBox;
15547         } else if((p = strstr(opt->name, " -file "))) {
15548             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15549             opt->textValue = p+7;
15550             opt->type = FileName; // FileName;
15551         } else if((p = strstr(opt->name, " -path "))) {
15552             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15553             opt->textValue = p+7;
15554             opt->type = PathName; // PathName;
15555         } else if(p = strstr(opt->name, " -check ")) {
15556             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15557             opt->value = (def != 0);
15558             opt->type = CheckBox;
15559         } else if(p = strstr(opt->name, " -combo ")) {
15560             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15561             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15562             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15563             opt->value = n = 0;
15564             while(q = StrStr(q, " /// ")) {
15565                 n++; *q = 0;    // count choices, and null-terminate each of them
15566                 q += 5;
15567                 if(*q == '*') { // remember default, which is marked with * prefix
15568                     q++;
15569                     opt->value = n;
15570                 }
15571                 cps->comboList[cps->comboCnt++] = q;
15572             }
15573             cps->comboList[cps->comboCnt++] = NULL;
15574             opt->max = n + 1;
15575             opt->type = ComboBox;
15576         } else if(p = strstr(opt->name, " -button")) {
15577             opt->type = Button;
15578         } else if(p = strstr(opt->name, " -save")) {
15579             opt->type = SaveButton;
15580         } else return FALSE;
15581         *p = 0; // terminate option name
15582         // now look if the command-line options define a setting for this engine option.
15583         if(cps->optionSettings && cps->optionSettings[0])
15584             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15585         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15586           snprintf(buf, MSG_SIZ, "option %s", p);
15587                 if(p = strstr(buf, ",")) *p = 0;
15588                 if(q = strchr(buf, '=')) switch(opt->type) {
15589                     case ComboBox:
15590                         for(n=0; n<opt->max; n++)
15591                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15592                         break;
15593                     case TextBox:
15594                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15595                         break;
15596                     case Spin:
15597                     case CheckBox:
15598                         opt->value = atoi(q+1);
15599                     default:
15600                         break;
15601                 }
15602                 strcat(buf, "\n");
15603                 SendToProgram(buf, cps);
15604         }
15605         return TRUE;
15606 }
15607
15608 void
15609 FeatureDone (ChessProgramState *cps, int val)
15610 {
15611   DelayedEventCallback cb = GetDelayedEvent();
15612   if ((cb == InitBackEnd3 && cps == &first) ||
15613       (cb == SettingsMenuIfReady && cps == &second) ||
15614       (cb == LoadEngine) ||
15615       (cb == TwoMachinesEventIfReady)) {
15616     CancelDelayedEvent();
15617     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15618   }
15619   cps->initDone = val;
15620 }
15621
15622 /* Parse feature command from engine */
15623 void
15624 ParseFeatures (char *args, ChessProgramState *cps)
15625 {
15626   char *p = args;
15627   char *q;
15628   int val;
15629   char buf[MSG_SIZ];
15630
15631   for (;;) {
15632     while (*p == ' ') p++;
15633     if (*p == NULLCHAR) return;
15634
15635     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15636     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15637     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15638     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15639     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15640     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15641     if (BoolFeature(&p, "reuse", &val, cps)) {
15642       /* Engine can disable reuse, but can't enable it if user said no */
15643       if (!val) cps->reuse = FALSE;
15644       continue;
15645     }
15646     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15647     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15648       if (gameMode == TwoMachinesPlay) {
15649         DisplayTwoMachinesTitle();
15650       } else {
15651         DisplayTitle("");
15652       }
15653       continue;
15654     }
15655     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15656     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15657     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15658     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15659     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15660     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15661     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15662     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15663     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15664     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15665     if (IntFeature(&p, "done", &val, cps)) {
15666       FeatureDone(cps, val);
15667       continue;
15668     }
15669     /* Added by Tord: */
15670     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15671     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15672     /* End of additions by Tord */
15673
15674     /* [HGM] added features: */
15675     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15676     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15677     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15678     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15679     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15680     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15681     if (StringFeature(&p, "option", buf, cps)) {
15682         FREE(cps->option[cps->nrOptions].name);
15683         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15684         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15685         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15686           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15687             SendToProgram(buf, cps);
15688             continue;
15689         }
15690         if(cps->nrOptions >= MAX_OPTIONS) {
15691             cps->nrOptions--;
15692             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15693             DisplayError(buf, 0);
15694         }
15695         continue;
15696     }
15697     /* End of additions by HGM */
15698
15699     /* unknown feature: complain and skip */
15700     q = p;
15701     while (*q && *q != '=') q++;
15702     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15703     SendToProgram(buf, cps);
15704     p = q;
15705     if (*p == '=') {
15706       p++;
15707       if (*p == '\"') {
15708         p++;
15709         while (*p && *p != '\"') p++;
15710         if (*p == '\"') p++;
15711       } else {
15712         while (*p && *p != ' ') p++;
15713       }
15714     }
15715   }
15716
15717 }
15718
15719 void
15720 PeriodicUpdatesEvent (int newState)
15721 {
15722     if (newState == appData.periodicUpdates)
15723       return;
15724
15725     appData.periodicUpdates=newState;
15726
15727     /* Display type changes, so update it now */
15728 //    DisplayAnalysis();
15729
15730     /* Get the ball rolling again... */
15731     if (newState) {
15732         AnalysisPeriodicEvent(1);
15733         StartAnalysisClock();
15734     }
15735 }
15736
15737 void
15738 PonderNextMoveEvent (int newState)
15739 {
15740     if (newState == appData.ponderNextMove) return;
15741     if (gameMode == EditPosition) EditPositionDone(TRUE);
15742     if (newState) {
15743         SendToProgram("hard\n", &first);
15744         if (gameMode == TwoMachinesPlay) {
15745             SendToProgram("hard\n", &second);
15746         }
15747     } else {
15748         SendToProgram("easy\n", &first);
15749         thinkOutput[0] = NULLCHAR;
15750         if (gameMode == TwoMachinesPlay) {
15751             SendToProgram("easy\n", &second);
15752         }
15753     }
15754     appData.ponderNextMove = newState;
15755 }
15756
15757 void
15758 NewSettingEvent (int option, int *feature, char *command, int value)
15759 {
15760     char buf[MSG_SIZ];
15761
15762     if (gameMode == EditPosition) EditPositionDone(TRUE);
15763     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15764     if(feature == NULL || *feature) SendToProgram(buf, &first);
15765     if (gameMode == TwoMachinesPlay) {
15766         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15767     }
15768 }
15769
15770 void
15771 ShowThinkingEvent ()
15772 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15773 {
15774     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15775     int newState = appData.showThinking
15776         // [HGM] thinking: other features now need thinking output as well
15777         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15778
15779     if (oldState == newState) return;
15780     oldState = newState;
15781     if (gameMode == EditPosition) EditPositionDone(TRUE);
15782     if (oldState) {
15783         SendToProgram("post\n", &first);
15784         if (gameMode == TwoMachinesPlay) {
15785             SendToProgram("post\n", &second);
15786         }
15787     } else {
15788         SendToProgram("nopost\n", &first);
15789         thinkOutput[0] = NULLCHAR;
15790         if (gameMode == TwoMachinesPlay) {
15791             SendToProgram("nopost\n", &second);
15792         }
15793     }
15794 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15795 }
15796
15797 void
15798 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15799 {
15800   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15801   if (pr == NoProc) return;
15802   AskQuestion(title, question, replyPrefix, pr);
15803 }
15804
15805 void
15806 TypeInEvent (char firstChar)
15807 {
15808     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15809         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15810         gameMode == AnalyzeMode || gameMode == EditGame || 
15811         gameMode == EditPosition || gameMode == IcsExamining ||
15812         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15813         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15814                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15815                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15816         gameMode == Training) PopUpMoveDialog(firstChar);
15817 }
15818
15819 void
15820 TypeInDoneEvent (char *move)
15821 {
15822         Board board;
15823         int n, fromX, fromY, toX, toY;
15824         char promoChar;
15825         ChessMove moveType;
15826
15827         // [HGM] FENedit
15828         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15829                 EditPositionPasteFEN(move);
15830                 return;
15831         }
15832         // [HGM] movenum: allow move number to be typed in any mode
15833         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15834           ToNrEvent(2*n-1);
15835           return;
15836         }
15837         // undocumented kludge: allow command-line option to be typed in!
15838         // (potentially fatal, and does not implement the effect of the option.)
15839         // should only be used for options that are values on which future decisions will be made,
15840         // and definitely not on options that would be used during initialization.
15841         if(strstr(move, "!!! -") == move) {
15842             ParseArgsFromString(move+4);
15843             return;
15844         }
15845
15846       if (gameMode != EditGame && currentMove != forwardMostMove && 
15847         gameMode != Training) {
15848         DisplayMoveError(_("Displayed move is not current"));
15849       } else {
15850         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15851           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15852         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15853         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15854           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15855           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15856         } else {
15857           DisplayMoveError(_("Could not parse move"));
15858         }
15859       }
15860 }
15861
15862 void
15863 DisplayMove (int moveNumber)
15864 {
15865     char message[MSG_SIZ];
15866     char res[MSG_SIZ];
15867     char cpThinkOutput[MSG_SIZ];
15868
15869     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15870
15871     if (moveNumber == forwardMostMove - 1 ||
15872         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15873
15874         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15875
15876         if (strchr(cpThinkOutput, '\n')) {
15877             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15878         }
15879     } else {
15880         *cpThinkOutput = NULLCHAR;
15881     }
15882
15883     /* [AS] Hide thinking from human user */
15884     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15885         *cpThinkOutput = NULLCHAR;
15886         if( thinkOutput[0] != NULLCHAR ) {
15887             int i;
15888
15889             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15890                 cpThinkOutput[i] = '.';
15891             }
15892             cpThinkOutput[i] = NULLCHAR;
15893             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15894         }
15895     }
15896
15897     if (moveNumber == forwardMostMove - 1 &&
15898         gameInfo.resultDetails != NULL) {
15899         if (gameInfo.resultDetails[0] == NULLCHAR) {
15900           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15901         } else {
15902           snprintf(res, MSG_SIZ, " {%s} %s",
15903                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15904         }
15905     } else {
15906         res[0] = NULLCHAR;
15907     }
15908
15909     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15910         DisplayMessage(res, cpThinkOutput);
15911     } else {
15912       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15913                 WhiteOnMove(moveNumber) ? " " : ".. ",
15914                 parseList[moveNumber], res);
15915         DisplayMessage(message, cpThinkOutput);
15916     }
15917 }
15918
15919 void
15920 DisplayComment (int moveNumber, char *text)
15921 {
15922     char title[MSG_SIZ];
15923
15924     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15925       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15926     } else {
15927       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15928               WhiteOnMove(moveNumber) ? " " : ".. ",
15929               parseList[moveNumber]);
15930     }
15931     if (text != NULL && (appData.autoDisplayComment || commentUp))
15932         CommentPopUp(title, text);
15933 }
15934
15935 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15936  * might be busy thinking or pondering.  It can be omitted if your
15937  * gnuchess is configured to stop thinking immediately on any user
15938  * input.  However, that gnuchess feature depends on the FIONREAD
15939  * ioctl, which does not work properly on some flavors of Unix.
15940  */
15941 void
15942 Attention (ChessProgramState *cps)
15943 {
15944 #if ATTENTION
15945     if (!cps->useSigint) return;
15946     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15947     switch (gameMode) {
15948       case MachinePlaysWhite:
15949       case MachinePlaysBlack:
15950       case TwoMachinesPlay:
15951       case IcsPlayingWhite:
15952       case IcsPlayingBlack:
15953       case AnalyzeMode:
15954       case AnalyzeFile:
15955         /* Skip if we know it isn't thinking */
15956         if (!cps->maybeThinking) return;
15957         if (appData.debugMode)
15958           fprintf(debugFP, "Interrupting %s\n", cps->which);
15959         InterruptChildProcess(cps->pr);
15960         cps->maybeThinking = FALSE;
15961         break;
15962       default:
15963         break;
15964     }
15965 #endif /*ATTENTION*/
15966 }
15967
15968 int
15969 CheckFlags ()
15970 {
15971     if (whiteTimeRemaining <= 0) {
15972         if (!whiteFlag) {
15973             whiteFlag = TRUE;
15974             if (appData.icsActive) {
15975                 if (appData.autoCallFlag &&
15976                     gameMode == IcsPlayingBlack && !blackFlag) {
15977                   SendToICS(ics_prefix);
15978                   SendToICS("flag\n");
15979                 }
15980             } else {
15981                 if (blackFlag) {
15982                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15983                 } else {
15984                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15985                     if (appData.autoCallFlag) {
15986                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15987                         return TRUE;
15988                     }
15989                 }
15990             }
15991         }
15992     }
15993     if (blackTimeRemaining <= 0) {
15994         if (!blackFlag) {
15995             blackFlag = TRUE;
15996             if (appData.icsActive) {
15997                 if (appData.autoCallFlag &&
15998                     gameMode == IcsPlayingWhite && !whiteFlag) {
15999                   SendToICS(ics_prefix);
16000                   SendToICS("flag\n");
16001                 }
16002             } else {
16003                 if (whiteFlag) {
16004                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16005                 } else {
16006                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16007                     if (appData.autoCallFlag) {
16008                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16009                         return TRUE;
16010                     }
16011                 }
16012             }
16013         }
16014     }
16015     return FALSE;
16016 }
16017
16018 void
16019 CheckTimeControl ()
16020 {
16021     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16022         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16023
16024     /*
16025      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16026      */
16027     if ( !WhiteOnMove(forwardMostMove) ) {
16028         /* White made time control */
16029         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16030         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16031         /* [HGM] time odds: correct new time quota for time odds! */
16032                                             / WhitePlayer()->timeOdds;
16033         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16034     } else {
16035         lastBlack -= blackTimeRemaining;
16036         /* Black made time control */
16037         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16038                                             / WhitePlayer()->other->timeOdds;
16039         lastWhite = whiteTimeRemaining;
16040     }
16041 }
16042
16043 void
16044 DisplayBothClocks ()
16045 {
16046     int wom = gameMode == EditPosition ?
16047       !blackPlaysFirst : WhiteOnMove(currentMove);
16048     DisplayWhiteClock(whiteTimeRemaining, wom);
16049     DisplayBlackClock(blackTimeRemaining, !wom);
16050 }
16051
16052
16053 /* Timekeeping seems to be a portability nightmare.  I think everyone
16054    has ftime(), but I'm really not sure, so I'm including some ifdefs
16055    to use other calls if you don't.  Clocks will be less accurate if
16056    you have neither ftime nor gettimeofday.
16057 */
16058
16059 /* VS 2008 requires the #include outside of the function */
16060 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16061 #include <sys/timeb.h>
16062 #endif
16063
16064 /* Get the current time as a TimeMark */
16065 void
16066 GetTimeMark (TimeMark *tm)
16067 {
16068 #if HAVE_GETTIMEOFDAY
16069
16070     struct timeval timeVal;
16071     struct timezone timeZone;
16072
16073     gettimeofday(&timeVal, &timeZone);
16074     tm->sec = (long) timeVal.tv_sec;
16075     tm->ms = (int) (timeVal.tv_usec / 1000L);
16076
16077 #else /*!HAVE_GETTIMEOFDAY*/
16078 #if HAVE_FTIME
16079
16080 // include <sys/timeb.h> / moved to just above start of function
16081     struct timeb timeB;
16082
16083     ftime(&timeB);
16084     tm->sec = (long) timeB.time;
16085     tm->ms = (int) timeB.millitm;
16086
16087 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16088     tm->sec = (long) time(NULL);
16089     tm->ms = 0;
16090 #endif
16091 #endif
16092 }
16093
16094 /* Return the difference in milliseconds between two
16095    time marks.  We assume the difference will fit in a long!
16096 */
16097 long
16098 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16099 {
16100     return 1000L*(tm2->sec - tm1->sec) +
16101            (long) (tm2->ms - tm1->ms);
16102 }
16103
16104
16105 /*
16106  * Code to manage the game clocks.
16107  *
16108  * In tournament play, black starts the clock and then white makes a move.
16109  * We give the human user a slight advantage if he is playing white---the
16110  * clocks don't run until he makes his first move, so it takes zero time.
16111  * Also, we don't account for network lag, so we could get out of sync
16112  * with GNU Chess's clock -- but then, referees are always right.
16113  */
16114
16115 static TimeMark tickStartTM;
16116 static long intendedTickLength;
16117
16118 long
16119 NextTickLength (long timeRemaining)
16120 {
16121     long nominalTickLength, nextTickLength;
16122
16123     if (timeRemaining > 0L && timeRemaining <= 10000L)
16124       nominalTickLength = 100L;
16125     else
16126       nominalTickLength = 1000L;
16127     nextTickLength = timeRemaining % nominalTickLength;
16128     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16129
16130     return nextTickLength;
16131 }
16132
16133 /* Adjust clock one minute up or down */
16134 void
16135 AdjustClock (Boolean which, int dir)
16136 {
16137     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16138     if(which) blackTimeRemaining += 60000*dir;
16139     else      whiteTimeRemaining += 60000*dir;
16140     DisplayBothClocks();
16141     adjustedClock = TRUE;
16142 }
16143
16144 /* Stop clocks and reset to a fresh time control */
16145 void
16146 ResetClocks ()
16147 {
16148     (void) StopClockTimer();
16149     if (appData.icsActive) {
16150         whiteTimeRemaining = blackTimeRemaining = 0;
16151     } else if (searchTime) {
16152         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16153         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16154     } else { /* [HGM] correct new time quote for time odds */
16155         whiteTC = blackTC = fullTimeControlString;
16156         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16157         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16158     }
16159     if (whiteFlag || blackFlag) {
16160         DisplayTitle("");
16161         whiteFlag = blackFlag = FALSE;
16162     }
16163     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16164     DisplayBothClocks();
16165     adjustedClock = FALSE;
16166 }
16167
16168 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16169
16170 /* Decrement running clock by amount of time that has passed */
16171 void
16172 DecrementClocks ()
16173 {
16174     long timeRemaining;
16175     long lastTickLength, fudge;
16176     TimeMark now;
16177
16178     if (!appData.clockMode) return;
16179     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16180
16181     GetTimeMark(&now);
16182
16183     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16184
16185     /* Fudge if we woke up a little too soon */
16186     fudge = intendedTickLength - lastTickLength;
16187     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16188
16189     if (WhiteOnMove(forwardMostMove)) {
16190         if(whiteNPS >= 0) lastTickLength = 0;
16191         timeRemaining = whiteTimeRemaining -= lastTickLength;
16192         if(timeRemaining < 0 && !appData.icsActive) {
16193             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16194             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16195                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16196                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16197             }
16198         }
16199         DisplayWhiteClock(whiteTimeRemaining - fudge,
16200                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16201     } else {
16202         if(blackNPS >= 0) lastTickLength = 0;
16203         timeRemaining = blackTimeRemaining -= lastTickLength;
16204         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16205             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16206             if(suddenDeath) {
16207                 blackStartMove = forwardMostMove;
16208                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16209             }
16210         }
16211         DisplayBlackClock(blackTimeRemaining - fudge,
16212                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16213     }
16214     if (CheckFlags()) return;
16215
16216     tickStartTM = now;
16217     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16218     StartClockTimer(intendedTickLength);
16219
16220     /* if the time remaining has fallen below the alarm threshold, sound the
16221      * alarm. if the alarm has sounded and (due to a takeback or time control
16222      * with increment) the time remaining has increased to a level above the
16223      * threshold, reset the alarm so it can sound again.
16224      */
16225
16226     if (appData.icsActive && appData.icsAlarm) {
16227
16228         /* make sure we are dealing with the user's clock */
16229         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16230                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16231            )) return;
16232
16233         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16234             alarmSounded = FALSE;
16235         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16236             PlayAlarmSound();
16237             alarmSounded = TRUE;
16238         }
16239     }
16240 }
16241
16242
16243 /* A player has just moved, so stop the previously running
16244    clock and (if in clock mode) start the other one.
16245    We redisplay both clocks in case we're in ICS mode, because
16246    ICS gives us an update to both clocks after every move.
16247    Note that this routine is called *after* forwardMostMove
16248    is updated, so the last fractional tick must be subtracted
16249    from the color that is *not* on move now.
16250 */
16251 void
16252 SwitchClocks (int newMoveNr)
16253 {
16254     long lastTickLength;
16255     TimeMark now;
16256     int flagged = FALSE;
16257
16258     GetTimeMark(&now);
16259
16260     if (StopClockTimer() && appData.clockMode) {
16261         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16262         if (!WhiteOnMove(forwardMostMove)) {
16263             if(blackNPS >= 0) lastTickLength = 0;
16264             blackTimeRemaining -= lastTickLength;
16265            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16266 //         if(pvInfoList[forwardMostMove].time == -1)
16267                  pvInfoList[forwardMostMove].time =               // use GUI time
16268                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16269         } else {
16270            if(whiteNPS >= 0) lastTickLength = 0;
16271            whiteTimeRemaining -= lastTickLength;
16272            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16273 //         if(pvInfoList[forwardMostMove].time == -1)
16274                  pvInfoList[forwardMostMove].time =
16275                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16276         }
16277         flagged = CheckFlags();
16278     }
16279     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16280     CheckTimeControl();
16281
16282     if (flagged || !appData.clockMode) return;
16283
16284     switch (gameMode) {
16285       case MachinePlaysBlack:
16286       case MachinePlaysWhite:
16287       case BeginningOfGame:
16288         if (pausing) return;
16289         break;
16290
16291       case EditGame:
16292       case PlayFromGameFile:
16293       case IcsExamining:
16294         return;
16295
16296       default:
16297         break;
16298     }
16299
16300     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16301         if(WhiteOnMove(forwardMostMove))
16302              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16303         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16304     }
16305
16306     tickStartTM = now;
16307     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16308       whiteTimeRemaining : blackTimeRemaining);
16309     StartClockTimer(intendedTickLength);
16310 }
16311
16312
16313 /* Stop both clocks */
16314 void
16315 StopClocks ()
16316 {
16317     long lastTickLength;
16318     TimeMark now;
16319
16320     if (!StopClockTimer()) return;
16321     if (!appData.clockMode) return;
16322
16323     GetTimeMark(&now);
16324
16325     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16326     if (WhiteOnMove(forwardMostMove)) {
16327         if(whiteNPS >= 0) lastTickLength = 0;
16328         whiteTimeRemaining -= lastTickLength;
16329         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16330     } else {
16331         if(blackNPS >= 0) lastTickLength = 0;
16332         blackTimeRemaining -= lastTickLength;
16333         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16334     }
16335     CheckFlags();
16336 }
16337
16338 /* Start clock of player on move.  Time may have been reset, so
16339    if clock is already running, stop and restart it. */
16340 void
16341 StartClocks ()
16342 {
16343     (void) StopClockTimer(); /* in case it was running already */
16344     DisplayBothClocks();
16345     if (CheckFlags()) return;
16346
16347     if (!appData.clockMode) return;
16348     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16349
16350     GetTimeMark(&tickStartTM);
16351     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16352       whiteTimeRemaining : blackTimeRemaining);
16353
16354    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16355     whiteNPS = blackNPS = -1;
16356     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16357        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16358         whiteNPS = first.nps;
16359     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16360        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16361         blackNPS = first.nps;
16362     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16363         whiteNPS = second.nps;
16364     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16365         blackNPS = second.nps;
16366     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16367
16368     StartClockTimer(intendedTickLength);
16369 }
16370
16371 char *
16372 TimeString (long ms)
16373 {
16374     long second, minute, hour, day;
16375     char *sign = "";
16376     static char buf[32];
16377
16378     if (ms > 0 && ms <= 9900) {
16379       /* convert milliseconds to tenths, rounding up */
16380       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16381
16382       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16383       return buf;
16384     }
16385
16386     /* convert milliseconds to seconds, rounding up */
16387     /* use floating point to avoid strangeness of integer division
16388        with negative dividends on many machines */
16389     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16390
16391     if (second < 0) {
16392         sign = "-";
16393         second = -second;
16394     }
16395
16396     day = second / (60 * 60 * 24);
16397     second = second % (60 * 60 * 24);
16398     hour = second / (60 * 60);
16399     second = second % (60 * 60);
16400     minute = second / 60;
16401     second = second % 60;
16402
16403     if (day > 0)
16404       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16405               sign, day, hour, minute, second);
16406     else if (hour > 0)
16407       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16408     else
16409       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16410
16411     return buf;
16412 }
16413
16414
16415 /*
16416  * This is necessary because some C libraries aren't ANSI C compliant yet.
16417  */
16418 char *
16419 StrStr (char *string, char *match)
16420 {
16421     int i, length;
16422
16423     length = strlen(match);
16424
16425     for (i = strlen(string) - length; i >= 0; i--, string++)
16426       if (!strncmp(match, string, length))
16427         return string;
16428
16429     return NULL;
16430 }
16431
16432 char *
16433 StrCaseStr (char *string, char *match)
16434 {
16435     int i, j, length;
16436
16437     length = strlen(match);
16438
16439     for (i = strlen(string) - length; i >= 0; i--, string++) {
16440         for (j = 0; j < length; j++) {
16441             if (ToLower(match[j]) != ToLower(string[j]))
16442               break;
16443         }
16444         if (j == length) return string;
16445     }
16446
16447     return NULL;
16448 }
16449
16450 #ifndef _amigados
16451 int
16452 StrCaseCmp (char *s1, char *s2)
16453 {
16454     char c1, c2;
16455
16456     for (;;) {
16457         c1 = ToLower(*s1++);
16458         c2 = ToLower(*s2++);
16459         if (c1 > c2) return 1;
16460         if (c1 < c2) return -1;
16461         if (c1 == NULLCHAR) return 0;
16462     }
16463 }
16464
16465
16466 int
16467 ToLower (int c)
16468 {
16469     return isupper(c) ? tolower(c) : c;
16470 }
16471
16472
16473 int
16474 ToUpper (int c)
16475 {
16476     return islower(c) ? toupper(c) : c;
16477 }
16478 #endif /* !_amigados    */
16479
16480 char *
16481 StrSave (char *s)
16482 {
16483   char *ret;
16484
16485   if ((ret = (char *) malloc(strlen(s) + 1)))
16486     {
16487       safeStrCpy(ret, s, strlen(s)+1);
16488     }
16489   return ret;
16490 }
16491
16492 char *
16493 StrSavePtr (char *s, char **savePtr)
16494 {
16495     if (*savePtr) {
16496         free(*savePtr);
16497     }
16498     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16499       safeStrCpy(*savePtr, s, strlen(s)+1);
16500     }
16501     return(*savePtr);
16502 }
16503
16504 char *
16505 PGNDate ()
16506 {
16507     time_t clock;
16508     struct tm *tm;
16509     char buf[MSG_SIZ];
16510
16511     clock = time((time_t *)NULL);
16512     tm = localtime(&clock);
16513     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16514             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16515     return StrSave(buf);
16516 }
16517
16518
16519 char *
16520 PositionToFEN (int move, char *overrideCastling)
16521 {
16522     int i, j, fromX, fromY, toX, toY;
16523     int whiteToPlay;
16524     char buf[MSG_SIZ];
16525     char *p, *q;
16526     int emptycount;
16527     ChessSquare piece;
16528
16529     whiteToPlay = (gameMode == EditPosition) ?
16530       !blackPlaysFirst : (move % 2 == 0);
16531     p = buf;
16532
16533     /* Piece placement data */
16534     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16535         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16536         emptycount = 0;
16537         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16538             if (boards[move][i][j] == EmptySquare) {
16539                 emptycount++;
16540             } else { ChessSquare piece = boards[move][i][j];
16541                 if (emptycount > 0) {
16542                     if(emptycount<10) /* [HGM] can be >= 10 */
16543                         *p++ = '0' + emptycount;
16544                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16545                     emptycount = 0;
16546                 }
16547                 if(PieceToChar(piece) == '+') {
16548                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16549                     *p++ = '+';
16550                     piece = (ChessSquare)(DEMOTED piece);
16551                 }
16552                 *p++ = PieceToChar(piece);
16553                 if(p[-1] == '~') {
16554                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16555                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16556                     *p++ = '~';
16557                 }
16558             }
16559         }
16560         if (emptycount > 0) {
16561             if(emptycount<10) /* [HGM] can be >= 10 */
16562                 *p++ = '0' + emptycount;
16563             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16564             emptycount = 0;
16565         }
16566         *p++ = '/';
16567     }
16568     *(p - 1) = ' ';
16569
16570     /* [HGM] print Crazyhouse or Shogi holdings */
16571     if( gameInfo.holdingsWidth ) {
16572         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16573         q = p;
16574         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16575             piece = boards[move][i][BOARD_WIDTH-1];
16576             if( piece != EmptySquare )
16577               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16578                   *p++ = PieceToChar(piece);
16579         }
16580         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16581             piece = boards[move][BOARD_HEIGHT-i-1][0];
16582             if( piece != EmptySquare )
16583               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16584                   *p++ = PieceToChar(piece);
16585         }
16586
16587         if( q == p ) *p++ = '-';
16588         *p++ = ']';
16589         *p++ = ' ';
16590     }
16591
16592     /* Active color */
16593     *p++ = whiteToPlay ? 'w' : 'b';
16594     *p++ = ' ';
16595
16596   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16597     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16598   } else {
16599   if(nrCastlingRights) {
16600      q = p;
16601      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16602        /* [HGM] write directly from rights */
16603            if(boards[move][CASTLING][2] != NoRights &&
16604               boards[move][CASTLING][0] != NoRights   )
16605                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16606            if(boards[move][CASTLING][2] != NoRights &&
16607               boards[move][CASTLING][1] != NoRights   )
16608                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16609            if(boards[move][CASTLING][5] != NoRights &&
16610               boards[move][CASTLING][3] != NoRights   )
16611                 *p++ = boards[move][CASTLING][3] + AAA;
16612            if(boards[move][CASTLING][5] != NoRights &&
16613               boards[move][CASTLING][4] != NoRights   )
16614                 *p++ = boards[move][CASTLING][4] + AAA;
16615      } else {
16616
16617         /* [HGM] write true castling rights */
16618         if( nrCastlingRights == 6 ) {
16619             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16620                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16621             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16622                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16623             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16624                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16625             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16626                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16627         }
16628      }
16629      if (q == p) *p++ = '-'; /* No castling rights */
16630      *p++ = ' ';
16631   }
16632
16633   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16634      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16635     /* En passant target square */
16636     if (move > backwardMostMove) {
16637         fromX = moveList[move - 1][0] - AAA;
16638         fromY = moveList[move - 1][1] - ONE;
16639         toX = moveList[move - 1][2] - AAA;
16640         toY = moveList[move - 1][3] - ONE;
16641         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16642             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16643             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16644             fromX == toX) {
16645             /* 2-square pawn move just happened */
16646             *p++ = toX + AAA;
16647             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16648         } else {
16649             *p++ = '-';
16650         }
16651     } else if(move == backwardMostMove) {
16652         // [HGM] perhaps we should always do it like this, and forget the above?
16653         if((signed char)boards[move][EP_STATUS] >= 0) {
16654             *p++ = boards[move][EP_STATUS] + AAA;
16655             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16656         } else {
16657             *p++ = '-';
16658         }
16659     } else {
16660         *p++ = '-';
16661     }
16662     *p++ = ' ';
16663   }
16664   }
16665
16666     /* [HGM] find reversible plies */
16667     {   int i = 0, j=move;
16668
16669         if (appData.debugMode) { int k;
16670             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16671             for(k=backwardMostMove; k<=forwardMostMove; k++)
16672                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16673
16674         }
16675
16676         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16677         if( j == backwardMostMove ) i += initialRulePlies;
16678         sprintf(p, "%d ", i);
16679         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16680     }
16681     /* Fullmove number */
16682     sprintf(p, "%d", (move / 2) + 1);
16683
16684     return StrSave(buf);
16685 }
16686
16687 Boolean
16688 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16689 {
16690     int i, j;
16691     char *p, c;
16692     int emptycount;
16693     ChessSquare piece;
16694
16695     p = fen;
16696
16697     /* [HGM] by default clear Crazyhouse holdings, if present */
16698     if(gameInfo.holdingsWidth) {
16699        for(i=0; i<BOARD_HEIGHT; i++) {
16700            board[i][0]             = EmptySquare; /* black holdings */
16701            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16702            board[i][1]             = (ChessSquare) 0; /* black counts */
16703            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16704        }
16705     }
16706
16707     /* Piece placement data */
16708     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16709         j = 0;
16710         for (;;) {
16711             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16712                 if (*p == '/') p++;
16713                 emptycount = gameInfo.boardWidth - j;
16714                 while (emptycount--)
16715                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16716                 break;
16717 #if(BOARD_FILES >= 10)
16718             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16719                 p++; emptycount=10;
16720                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16721                 while (emptycount--)
16722                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16723 #endif
16724             } else if (isdigit(*p)) {
16725                 emptycount = *p++ - '0';
16726                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16727                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16728                 while (emptycount--)
16729                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16730             } else if (*p == '+' || isalpha(*p)) {
16731                 if (j >= gameInfo.boardWidth) return FALSE;
16732                 if(*p=='+') {
16733                     piece = CharToPiece(*++p);
16734                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16735                     piece = (ChessSquare) (PROMOTED piece ); p++;
16736                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16737                 } else piece = CharToPiece(*p++);
16738
16739                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16740                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16741                     piece = (ChessSquare) (PROMOTED piece);
16742                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16743                     p++;
16744                 }
16745                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16746             } else {
16747                 return FALSE;
16748             }
16749         }
16750     }
16751     while (*p == '/' || *p == ' ') p++;
16752
16753     /* [HGM] look for Crazyhouse holdings here */
16754     while(*p==' ') p++;
16755     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16756         if(*p == '[') p++;
16757         if(*p == '-' ) p++; /* empty holdings */ else {
16758             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16759             /* if we would allow FEN reading to set board size, we would   */
16760             /* have to add holdings and shift the board read so far here   */
16761             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16762                 p++;
16763                 if((int) piece >= (int) BlackPawn ) {
16764                     i = (int)piece - (int)BlackPawn;
16765                     i = PieceToNumber((ChessSquare)i);
16766                     if( i >= gameInfo.holdingsSize ) return FALSE;
16767                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16768                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16769                 } else {
16770                     i = (int)piece - (int)WhitePawn;
16771                     i = PieceToNumber((ChessSquare)i);
16772                     if( i >= gameInfo.holdingsSize ) return FALSE;
16773                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16774                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16775                 }
16776             }
16777         }
16778         if(*p == ']') p++;
16779     }
16780
16781     while(*p == ' ') p++;
16782
16783     /* Active color */
16784     c = *p++;
16785     if(appData.colorNickNames) {
16786       if( c == appData.colorNickNames[0] ) c = 'w'; else
16787       if( c == appData.colorNickNames[1] ) c = 'b';
16788     }
16789     switch (c) {
16790       case 'w':
16791         *blackPlaysFirst = FALSE;
16792         break;
16793       case 'b':
16794         *blackPlaysFirst = TRUE;
16795         break;
16796       default:
16797         return FALSE;
16798     }
16799
16800     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16801     /* return the extra info in global variiables             */
16802
16803     /* set defaults in case FEN is incomplete */
16804     board[EP_STATUS] = EP_UNKNOWN;
16805     for(i=0; i<nrCastlingRights; i++ ) {
16806         board[CASTLING][i] =
16807             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16808     }   /* assume possible unless obviously impossible */
16809     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16810     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16811     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16812                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16813     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16814     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16815     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16816                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16817     FENrulePlies = 0;
16818
16819     while(*p==' ') p++;
16820     if(nrCastlingRights) {
16821       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16822           /* castling indicator present, so default becomes no castlings */
16823           for(i=0; i<nrCastlingRights; i++ ) {
16824                  board[CASTLING][i] = NoRights;
16825           }
16826       }
16827       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16828              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16829              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16830              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16831         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16832
16833         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16834             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16835             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16836         }
16837         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16838             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16839         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16840                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16841         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16842                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16843         switch(c) {
16844           case'K':
16845               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16846               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16847               board[CASTLING][2] = whiteKingFile;
16848               break;
16849           case'Q':
16850               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16851               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16852               board[CASTLING][2] = whiteKingFile;
16853               break;
16854           case'k':
16855               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16856               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16857               board[CASTLING][5] = blackKingFile;
16858               break;
16859           case'q':
16860               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16861               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16862               board[CASTLING][5] = blackKingFile;
16863           case '-':
16864               break;
16865           default: /* FRC castlings */
16866               if(c >= 'a') { /* black rights */
16867                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16868                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16869                   if(i == BOARD_RGHT) break;
16870                   board[CASTLING][5] = i;
16871                   c -= AAA;
16872                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16873                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16874                   if(c > i)
16875                       board[CASTLING][3] = c;
16876                   else
16877                       board[CASTLING][4] = c;
16878               } else { /* white rights */
16879                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16880                     if(board[0][i] == WhiteKing) break;
16881                   if(i == BOARD_RGHT) break;
16882                   board[CASTLING][2] = i;
16883                   c -= AAA - 'a' + 'A';
16884                   if(board[0][c] >= WhiteKing) break;
16885                   if(c > i)
16886                       board[CASTLING][0] = c;
16887                   else
16888                       board[CASTLING][1] = c;
16889               }
16890         }
16891       }
16892       for(i=0; i<nrCastlingRights; i++)
16893         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16894     if (appData.debugMode) {
16895         fprintf(debugFP, "FEN castling rights:");
16896         for(i=0; i<nrCastlingRights; i++)
16897         fprintf(debugFP, " %d", board[CASTLING][i]);
16898         fprintf(debugFP, "\n");
16899     }
16900
16901       while(*p==' ') p++;
16902     }
16903
16904     /* read e.p. field in games that know e.p. capture */
16905     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16906        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16907       if(*p=='-') {
16908         p++; board[EP_STATUS] = EP_NONE;
16909       } else {
16910          char c = *p++ - AAA;
16911
16912          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16913          if(*p >= '0' && *p <='9') p++;
16914          board[EP_STATUS] = c;
16915       }
16916     }
16917
16918
16919     if(sscanf(p, "%d", &i) == 1) {
16920         FENrulePlies = i; /* 50-move ply counter */
16921         /* (The move number is still ignored)    */
16922     }
16923
16924     return TRUE;
16925 }
16926
16927 void
16928 EditPositionPasteFEN (char *fen)
16929 {
16930   if (fen != NULL) {
16931     Board initial_position;
16932
16933     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16934       DisplayError(_("Bad FEN position in clipboard"), 0);
16935       return ;
16936     } else {
16937       int savedBlackPlaysFirst = blackPlaysFirst;
16938       EditPositionEvent();
16939       blackPlaysFirst = savedBlackPlaysFirst;
16940       CopyBoard(boards[0], initial_position);
16941       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16942       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16943       DisplayBothClocks();
16944       DrawPosition(FALSE, boards[currentMove]);
16945     }
16946   }
16947 }
16948
16949 static char cseq[12] = "\\   ";
16950
16951 Boolean
16952 set_cont_sequence (char *new_seq)
16953 {
16954     int len;
16955     Boolean ret;
16956
16957     // handle bad attempts to set the sequence
16958         if (!new_seq)
16959                 return 0; // acceptable error - no debug
16960
16961     len = strlen(new_seq);
16962     ret = (len > 0) && (len < sizeof(cseq));
16963     if (ret)
16964       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16965     else if (appData.debugMode)
16966       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16967     return ret;
16968 }
16969
16970 /*
16971     reformat a source message so words don't cross the width boundary.  internal
16972     newlines are not removed.  returns the wrapped size (no null character unless
16973     included in source message).  If dest is NULL, only calculate the size required
16974     for the dest buffer.  lp argument indicats line position upon entry, and it's
16975     passed back upon exit.
16976 */
16977 int
16978 wrap (char *dest, char *src, int count, int width, int *lp)
16979 {
16980     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16981
16982     cseq_len = strlen(cseq);
16983     old_line = line = *lp;
16984     ansi = len = clen = 0;
16985
16986     for (i=0; i < count; i++)
16987     {
16988         if (src[i] == '\033')
16989             ansi = 1;
16990
16991         // if we hit the width, back up
16992         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16993         {
16994             // store i & len in case the word is too long
16995             old_i = i, old_len = len;
16996
16997             // find the end of the last word
16998             while (i && src[i] != ' ' && src[i] != '\n')
16999             {
17000                 i--;
17001                 len--;
17002             }
17003
17004             // word too long?  restore i & len before splitting it
17005             if ((old_i-i+clen) >= width)
17006             {
17007                 i = old_i;
17008                 len = old_len;
17009             }
17010
17011             // extra space?
17012             if (i && src[i-1] == ' ')
17013                 len--;
17014
17015             if (src[i] != ' ' && src[i] != '\n')
17016             {
17017                 i--;
17018                 if (len)
17019                     len--;
17020             }
17021
17022             // now append the newline and continuation sequence
17023             if (dest)
17024                 dest[len] = '\n';
17025             len++;
17026             if (dest)
17027                 strncpy(dest+len, cseq, cseq_len);
17028             len += cseq_len;
17029             line = cseq_len;
17030             clen = cseq_len;
17031             continue;
17032         }
17033
17034         if (dest)
17035             dest[len] = src[i];
17036         len++;
17037         if (!ansi)
17038             line++;
17039         if (src[i] == '\n')
17040             line = 0;
17041         if (src[i] == 'm')
17042             ansi = 0;
17043     }
17044     if (dest && appData.debugMode)
17045     {
17046         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17047             count, width, line, len, *lp);
17048         show_bytes(debugFP, src, count);
17049         fprintf(debugFP, "\ndest: ");
17050         show_bytes(debugFP, dest, len);
17051         fprintf(debugFP, "\n");
17052     }
17053     *lp = dest ? line : old_line;
17054
17055     return len;
17056 }
17057
17058 // [HGM] vari: routines for shelving variations
17059 Boolean modeRestore = FALSE;
17060
17061 void
17062 PushInner (int firstMove, int lastMove)
17063 {
17064         int i, j, nrMoves = lastMove - firstMove;
17065
17066         // push current tail of game on stack
17067         savedResult[storedGames] = gameInfo.result;
17068         savedDetails[storedGames] = gameInfo.resultDetails;
17069         gameInfo.resultDetails = NULL;
17070         savedFirst[storedGames] = firstMove;
17071         savedLast [storedGames] = lastMove;
17072         savedFramePtr[storedGames] = framePtr;
17073         framePtr -= nrMoves; // reserve space for the boards
17074         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17075             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17076             for(j=0; j<MOVE_LEN; j++)
17077                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17078             for(j=0; j<2*MOVE_LEN; j++)
17079                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17080             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17081             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17082             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17083             pvInfoList[firstMove+i-1].depth = 0;
17084             commentList[framePtr+i] = commentList[firstMove+i];
17085             commentList[firstMove+i] = NULL;
17086         }
17087
17088         storedGames++;
17089         forwardMostMove = firstMove; // truncate game so we can start variation
17090 }
17091
17092 void
17093 PushTail (int firstMove, int lastMove)
17094 {
17095         if(appData.icsActive) { // only in local mode
17096                 forwardMostMove = currentMove; // mimic old ICS behavior
17097                 return;
17098         }
17099         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17100
17101         PushInner(firstMove, lastMove);
17102         if(storedGames == 1) GreyRevert(FALSE);
17103         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17104 }
17105
17106 void
17107 PopInner (Boolean annotate)
17108 {
17109         int i, j, nrMoves;
17110         char buf[8000], moveBuf[20];
17111
17112         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17113         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17114         nrMoves = savedLast[storedGames] - currentMove;
17115         if(annotate) {
17116                 int cnt = 10;
17117                 if(!WhiteOnMove(currentMove))
17118                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17119                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17120                 for(i=currentMove; i<forwardMostMove; i++) {
17121                         if(WhiteOnMove(i))
17122                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17123                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17124                         strcat(buf, moveBuf);
17125                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17126                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17127                 }
17128                 strcat(buf, ")");
17129         }
17130         for(i=1; i<=nrMoves; i++) { // copy last variation back
17131             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17132             for(j=0; j<MOVE_LEN; j++)
17133                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17134             for(j=0; j<2*MOVE_LEN; j++)
17135                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17136             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17137             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17138             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17139             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17140             commentList[currentMove+i] = commentList[framePtr+i];
17141             commentList[framePtr+i] = NULL;
17142         }
17143         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17144         framePtr = savedFramePtr[storedGames];
17145         gameInfo.result = savedResult[storedGames];
17146         if(gameInfo.resultDetails != NULL) {
17147             free(gameInfo.resultDetails);
17148       }
17149         gameInfo.resultDetails = savedDetails[storedGames];
17150         forwardMostMove = currentMove + nrMoves;
17151 }
17152
17153 Boolean
17154 PopTail (Boolean annotate)
17155 {
17156         if(appData.icsActive) return FALSE; // only in local mode
17157         if(!storedGames) return FALSE; // sanity
17158         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17159
17160         PopInner(annotate);
17161         if(currentMove < forwardMostMove) ForwardEvent(); else
17162         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17163
17164         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17165         return TRUE;
17166 }
17167
17168 void
17169 CleanupTail ()
17170 {       // remove all shelved variations
17171         int i;
17172         for(i=0; i<storedGames; i++) {
17173             if(savedDetails[i])
17174                 free(savedDetails[i]);
17175             savedDetails[i] = NULL;
17176         }
17177         for(i=framePtr; i<MAX_MOVES; i++) {
17178                 if(commentList[i]) free(commentList[i]);
17179                 commentList[i] = NULL;
17180         }
17181         framePtr = MAX_MOVES-1;
17182         storedGames = 0;
17183 }
17184
17185 void
17186 LoadVariation (int index, char *text)
17187 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17188         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17189         int level = 0, move;
17190
17191         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17192         // first find outermost bracketing variation
17193         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17194             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17195                 if(*p == '{') wait = '}'; else
17196                 if(*p == '[') wait = ']'; else
17197                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17198                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17199             }
17200             if(*p == wait) wait = NULLCHAR; // closing ]} found
17201             p++;
17202         }
17203         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17204         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17205         end[1] = NULLCHAR; // clip off comment beyond variation
17206         ToNrEvent(currentMove-1);
17207         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17208         // kludge: use ParsePV() to append variation to game
17209         move = currentMove;
17210         ParsePV(start, TRUE, TRUE);
17211         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17212         ClearPremoveHighlights();
17213         CommentPopDown();
17214         ToNrEvent(currentMove+1);
17215 }
17216