Improve arrow drawing
[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, activePartnerTime;
459 Boolean adjustedClock;
460 long timeControl_2; /* [AS] Allow separate time controls */
461 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [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       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4248       char *toSqr;
4249       for (k = 0; k < ranks; k++) {
4250         for (j = 0; j < files; j++)
4251           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4252         if(gameInfo.holdingsWidth > 1) {
4253              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4254              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4255         }
4256       }
4257       CopyBoard(partnerBoard, board);
4258       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4259         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4260         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4261       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4262       if(toSqr = strchr(str, '-')) {
4263         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4264         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4265       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4266       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4267       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4268       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4269       if(twoBoards) {
4270           DisplayWhiteClock(white_time*fac, to_play == 'W');
4271           DisplayBlackClock(black_time*fac, to_play != 'W');
4272           activePartner = to_play;
4273           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4274                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4275       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4276                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4277       DisplayMessage(partnerStatus, "");
4278         partnerBoardValid = TRUE;
4279       return;
4280     }
4281
4282     /* Modify behavior for initial board display on move listing
4283        of wild games.
4284        */
4285     switch (ics_getting_history) {
4286       case H_FALSE:
4287       case H_REQUESTED:
4288         break;
4289       case H_GOT_REQ_HEADER:
4290       case H_GOT_UNREQ_HEADER:
4291         /* This is the initial position of the current game */
4292         gamenum = ics_gamenum;
4293         moveNum = 0;            /* old ICS bug workaround */
4294         if (to_play == 'B') {
4295           startedFromSetupPosition = TRUE;
4296           blackPlaysFirst = TRUE;
4297           moveNum = 1;
4298           if (forwardMostMove == 0) forwardMostMove = 1;
4299           if (backwardMostMove == 0) backwardMostMove = 1;
4300           if (currentMove == 0) currentMove = 1;
4301         }
4302         newGameMode = gameMode;
4303         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4304         break;
4305       case H_GOT_UNWANTED_HEADER:
4306         /* This is an initial board that we don't want */
4307         return;
4308       case H_GETTING_MOVES:
4309         /* Should not happen */
4310         DisplayError(_("Error gathering move list: extra board"), 0);
4311         ics_getting_history = H_FALSE;
4312         return;
4313     }
4314
4315    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4316                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4317      /* [HGM] We seem to have switched variant unexpectedly
4318       * Try to guess new variant from board size
4319       */
4320           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4321           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4322           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4323           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4324           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4325           if(!weird) newVariant = VariantNormal;
4326           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4327           /* Get a move list just to see the header, which
4328              will tell us whether this is really bug or zh */
4329           if (ics_getting_history == H_FALSE) {
4330             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4331             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4332             SendToICS(str);
4333           }
4334     }
4335
4336     /* Take action if this is the first board of a new game, or of a
4337        different game than is currently being displayed.  */
4338     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4339         relation == RELATION_ISOLATED_BOARD) {
4340
4341         /* Forget the old game and get the history (if any) of the new one */
4342         if (gameMode != BeginningOfGame) {
4343           Reset(TRUE, TRUE);
4344         }
4345         newGame = TRUE;
4346         if (appData.autoRaiseBoard) BoardToTop();
4347         prevMove = -3;
4348         if (gamenum == -1) {
4349             newGameMode = IcsIdle;
4350         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4351                    appData.getMoveList && !reqFlag) {
4352             /* Need to get game history */
4353             ics_getting_history = H_REQUESTED;
4354             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4355             SendToICS(str);
4356         }
4357
4358         /* Initially flip the board to have black on the bottom if playing
4359            black or if the ICS flip flag is set, but let the user change
4360            it with the Flip View button. */
4361         flipView = appData.autoFlipView ?
4362           (newGameMode == IcsPlayingBlack) || ics_flip :
4363           appData.flipView;
4364
4365         /* Done with values from previous mode; copy in new ones */
4366         gameMode = newGameMode;
4367         ModeHighlight();
4368         ics_gamenum = gamenum;
4369         if (gamenum == gs_gamenum) {
4370             int klen = strlen(gs_kind);
4371             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4372             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4373             gameInfo.event = StrSave(str);
4374         } else {
4375             gameInfo.event = StrSave("ICS game");
4376         }
4377         gameInfo.site = StrSave(appData.icsHost);
4378         gameInfo.date = PGNDate();
4379         gameInfo.round = StrSave("-");
4380         gameInfo.white = StrSave(white);
4381         gameInfo.black = StrSave(black);
4382         timeControl = basetime * 60 * 1000;
4383         timeControl_2 = 0;
4384         timeIncrement = increment * 1000;
4385         movesPerSession = 0;
4386         gameInfo.timeControl = TimeControlTagValue();
4387         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4388   if (appData.debugMode) {
4389     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4390     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4391     setbuf(debugFP, NULL);
4392   }
4393
4394         gameInfo.outOfBook = NULL;
4395
4396         /* Do we have the ratings? */
4397         if (strcmp(player1Name, white) == 0 &&
4398             strcmp(player2Name, black) == 0) {
4399             if (appData.debugMode)
4400               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4401                       player1Rating, player2Rating);
4402             gameInfo.whiteRating = player1Rating;
4403             gameInfo.blackRating = player2Rating;
4404         } else if (strcmp(player2Name, white) == 0 &&
4405                    strcmp(player1Name, black) == 0) {
4406             if (appData.debugMode)
4407               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4408                       player2Rating, player1Rating);
4409             gameInfo.whiteRating = player2Rating;
4410             gameInfo.blackRating = player1Rating;
4411         }
4412         player1Name[0] = player2Name[0] = NULLCHAR;
4413
4414         /* Silence shouts if requested */
4415         if (appData.quietPlay &&
4416             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4417             SendToICS(ics_prefix);
4418             SendToICS("set shout 0\n");
4419         }
4420     }
4421
4422     /* Deal with midgame name changes */
4423     if (!newGame) {
4424         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4425             if (gameInfo.white) free(gameInfo.white);
4426             gameInfo.white = StrSave(white);
4427         }
4428         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4429             if (gameInfo.black) free(gameInfo.black);
4430             gameInfo.black = StrSave(black);
4431         }
4432     }
4433
4434     /* Throw away game result if anything actually changes in examine mode */
4435     if (gameMode == IcsExamining && !newGame) {
4436         gameInfo.result = GameUnfinished;
4437         if (gameInfo.resultDetails != NULL) {
4438             free(gameInfo.resultDetails);
4439             gameInfo.resultDetails = NULL;
4440         }
4441     }
4442
4443     /* In pausing && IcsExamining mode, we ignore boards coming
4444        in if they are in a different variation than we are. */
4445     if (pauseExamInvalid) return;
4446     if (pausing && gameMode == IcsExamining) {
4447         if (moveNum <= pauseExamForwardMostMove) {
4448             pauseExamInvalid = TRUE;
4449             forwardMostMove = pauseExamForwardMostMove;
4450             return;
4451         }
4452     }
4453
4454   if (appData.debugMode) {
4455     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4456   }
4457     /* Parse the board */
4458     for (k = 0; k < ranks; k++) {
4459       for (j = 0; j < files; j++)
4460         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4461       if(gameInfo.holdingsWidth > 1) {
4462            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4463            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4464       }
4465     }
4466     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4467       board[5][BOARD_RGHT+1] = WhiteAngel;
4468       board[6][BOARD_RGHT+1] = WhiteMarshall;
4469       board[1][0] = BlackMarshall;
4470       board[2][0] = BlackAngel;
4471       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4472     }
4473     CopyBoard(boards[moveNum], board);
4474     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4475     if (moveNum == 0) {
4476         startedFromSetupPosition =
4477           !CompareBoards(board, initialPosition);
4478         if(startedFromSetupPosition)
4479             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4480     }
4481
4482     /* [HGM] Set castling rights. Take the outermost Rooks,
4483        to make it also work for FRC opening positions. Note that board12
4484        is really defective for later FRC positions, as it has no way to
4485        indicate which Rook can castle if they are on the same side of King.
4486        For the initial position we grant rights to the outermost Rooks,
4487        and remember thos rights, and we then copy them on positions
4488        later in an FRC game. This means WB might not recognize castlings with
4489        Rooks that have moved back to their original position as illegal,
4490        but in ICS mode that is not its job anyway.
4491     */
4492     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4493     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4494
4495         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4496             if(board[0][i] == WhiteRook) j = i;
4497         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4498         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4499             if(board[0][i] == WhiteRook) j = i;
4500         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4501         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4502             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4503         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4504         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4505             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4506         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4507
4508         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4509         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4510         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4511             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4512         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4513             if(board[BOARD_HEIGHT-1][k] == bKing)
4514                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4515         if(gameInfo.variant == VariantTwoKings) {
4516             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4517             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4518             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4519         }
4520     } else { int r;
4521         r = boards[moveNum][CASTLING][0] = initialRights[0];
4522         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4523         r = boards[moveNum][CASTLING][1] = initialRights[1];
4524         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4525         r = boards[moveNum][CASTLING][3] = initialRights[3];
4526         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4527         r = boards[moveNum][CASTLING][4] = initialRights[4];
4528         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4529         /* wildcastle kludge: always assume King has rights */
4530         r = boards[moveNum][CASTLING][2] = initialRights[2];
4531         r = boards[moveNum][CASTLING][5] = initialRights[5];
4532     }
4533     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4534     boards[moveNum][EP_STATUS] = EP_NONE;
4535     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4536     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4537     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4538
4539
4540     if (ics_getting_history == H_GOT_REQ_HEADER ||
4541         ics_getting_history == H_GOT_UNREQ_HEADER) {
4542         /* This was an initial position from a move list, not
4543            the current position */
4544         return;
4545     }
4546
4547     /* Update currentMove and known move number limits */
4548     newMove = newGame || moveNum > forwardMostMove;
4549
4550     if (newGame) {
4551         forwardMostMove = backwardMostMove = currentMove = moveNum;
4552         if (gameMode == IcsExamining && moveNum == 0) {
4553           /* Workaround for ICS limitation: we are not told the wild
4554              type when starting to examine a game.  But if we ask for
4555              the move list, the move list header will tell us */
4556             ics_getting_history = H_REQUESTED;
4557             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4558             SendToICS(str);
4559         }
4560     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4561                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4562 #if ZIPPY
4563         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4564         /* [HGM] applied this also to an engine that is silently watching        */
4565         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4566             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4567             gameInfo.variant == currentlyInitializedVariant) {
4568           takeback = forwardMostMove - moveNum;
4569           for (i = 0; i < takeback; i++) {
4570             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4571             SendToProgram("undo\n", &first);
4572           }
4573         }
4574 #endif
4575
4576         forwardMostMove = moveNum;
4577         if (!pausing || currentMove > forwardMostMove)
4578           currentMove = forwardMostMove;
4579     } else {
4580         /* New part of history that is not contiguous with old part */
4581         if (pausing && gameMode == IcsExamining) {
4582             pauseExamInvalid = TRUE;
4583             forwardMostMove = pauseExamForwardMostMove;
4584             return;
4585         }
4586         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4587 #if ZIPPY
4588             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4589                 // [HGM] when we will receive the move list we now request, it will be
4590                 // fed to the engine from the first move on. So if the engine is not
4591                 // in the initial position now, bring it there.
4592                 InitChessProgram(&first, 0);
4593             }
4594 #endif
4595             ics_getting_history = H_REQUESTED;
4596             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4597             SendToICS(str);
4598         }
4599         forwardMostMove = backwardMostMove = currentMove = moveNum;
4600     }
4601
4602     /* Update the clocks */
4603     if (strchr(elapsed_time, '.')) {
4604       /* Time is in ms */
4605       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4606       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4607     } else {
4608       /* Time is in seconds */
4609       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4610       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4611     }
4612
4613
4614 #if ZIPPY
4615     if (appData.zippyPlay && newGame &&
4616         gameMode != IcsObserving && gameMode != IcsIdle &&
4617         gameMode != IcsExamining)
4618       ZippyFirstBoard(moveNum, basetime, increment);
4619 #endif
4620
4621     /* Put the move on the move list, first converting
4622        to canonical algebraic form. */
4623     if (moveNum > 0) {
4624   if (appData.debugMode) {
4625     if (appData.debugMode) { int f = forwardMostMove;
4626         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4627                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4628                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4629     }
4630     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4631     fprintf(debugFP, "moveNum = %d\n", moveNum);
4632     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4633     setbuf(debugFP, NULL);
4634   }
4635         if (moveNum <= backwardMostMove) {
4636             /* We don't know what the board looked like before
4637                this move.  Punt. */
4638           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4639             strcat(parseList[moveNum - 1], " ");
4640             strcat(parseList[moveNum - 1], elapsed_time);
4641             moveList[moveNum - 1][0] = NULLCHAR;
4642         } else if (strcmp(move_str, "none") == 0) {
4643             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4644             /* Again, we don't know what the board looked like;
4645                this is really the start of the game. */
4646             parseList[moveNum - 1][0] = NULLCHAR;
4647             moveList[moveNum - 1][0] = NULLCHAR;
4648             backwardMostMove = moveNum;
4649             startedFromSetupPosition = TRUE;
4650             fromX = fromY = toX = toY = -1;
4651         } else {
4652           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4653           //                 So we parse the long-algebraic move string in stead of the SAN move
4654           int valid; char buf[MSG_SIZ], *prom;
4655
4656           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4657                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4658           // str looks something like "Q/a1-a2"; kill the slash
4659           if(str[1] == '/')
4660             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4661           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4662           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4663                 strcat(buf, prom); // long move lacks promo specification!
4664           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4665                 if(appData.debugMode)
4666                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4667                 safeStrCpy(move_str, buf, MSG_SIZ);
4668           }
4669           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4670                                 &fromX, &fromY, &toX, &toY, &promoChar)
4671                || ParseOneMove(buf, moveNum - 1, &moveType,
4672                                 &fromX, &fromY, &toX, &toY, &promoChar);
4673           // end of long SAN patch
4674           if (valid) {
4675             (void) CoordsToAlgebraic(boards[moveNum - 1],
4676                                      PosFlags(moveNum - 1),
4677                                      fromY, fromX, toY, toX, promoChar,
4678                                      parseList[moveNum-1]);
4679             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4680               case MT_NONE:
4681               case MT_STALEMATE:
4682               default:
4683                 break;
4684               case MT_CHECK:
4685                 if(gameInfo.variant != VariantShogi)
4686                     strcat(parseList[moveNum - 1], "+");
4687                 break;
4688               case MT_CHECKMATE:
4689               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4690                 strcat(parseList[moveNum - 1], "#");
4691                 break;
4692             }
4693             strcat(parseList[moveNum - 1], " ");
4694             strcat(parseList[moveNum - 1], elapsed_time);
4695             /* currentMoveString is set as a side-effect of ParseOneMove */
4696             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4697             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4698             strcat(moveList[moveNum - 1], "\n");
4699
4700             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4701                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4702               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4703                 ChessSquare old, new = boards[moveNum][k][j];
4704                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4705                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4706                   if(old == new) continue;
4707                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4708                   else if(new == WhiteWazir || new == BlackWazir) {
4709                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4710                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4711                       else boards[moveNum][k][j] = old; // preserve type of Gold
4712                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4713                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4714               }
4715           } else {
4716             /* Move from ICS was illegal!?  Punt. */
4717             if (appData.debugMode) {
4718               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4719               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4720             }
4721             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4722             strcat(parseList[moveNum - 1], " ");
4723             strcat(parseList[moveNum - 1], elapsed_time);
4724             moveList[moveNum - 1][0] = NULLCHAR;
4725             fromX = fromY = toX = toY = -1;
4726           }
4727         }
4728   if (appData.debugMode) {
4729     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4730     setbuf(debugFP, NULL);
4731   }
4732
4733 #if ZIPPY
4734         /* Send move to chess program (BEFORE animating it). */
4735         if (appData.zippyPlay && !newGame && newMove &&
4736            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4737
4738             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4739                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4740                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4741                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4742                             move_str);
4743                     DisplayError(str, 0);
4744                 } else {
4745                     if (first.sendTime) {
4746                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4747                     }
4748                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4749                     if (firstMove && !bookHit) {
4750                         firstMove = FALSE;
4751                         if (first.useColors) {
4752                           SendToProgram(gameMode == IcsPlayingWhite ?
4753                                         "white\ngo\n" :
4754                                         "black\ngo\n", &first);
4755                         } else {
4756                           SendToProgram("go\n", &first);
4757                         }
4758                         first.maybeThinking = TRUE;
4759                     }
4760                 }
4761             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4762               if (moveList[moveNum - 1][0] == NULLCHAR) {
4763                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4764                 DisplayError(str, 0);
4765               } else {
4766                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4767                 SendMoveToProgram(moveNum - 1, &first);
4768               }
4769             }
4770         }
4771 #endif
4772     }
4773
4774     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4775         /* If move comes from a remote source, animate it.  If it
4776            isn't remote, it will have already been animated. */
4777         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4778             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4779         }
4780         if (!pausing && appData.highlightLastMove) {
4781             SetHighlights(fromX, fromY, toX, toY);
4782         }
4783     }
4784
4785     /* Start the clocks */
4786     whiteFlag = blackFlag = FALSE;
4787     appData.clockMode = !(basetime == 0 && increment == 0);
4788     if (ticking == 0) {
4789       ics_clock_paused = TRUE;
4790       StopClocks();
4791     } else if (ticking == 1) {
4792       ics_clock_paused = FALSE;
4793     }
4794     if (gameMode == IcsIdle ||
4795         relation == RELATION_OBSERVING_STATIC ||
4796         relation == RELATION_EXAMINING ||
4797         ics_clock_paused)
4798       DisplayBothClocks();
4799     else
4800       StartClocks();
4801
4802     /* Display opponents and material strengths */
4803     if (gameInfo.variant != VariantBughouse &&
4804         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4805         if (tinyLayout || smallLayout) {
4806             if(gameInfo.variant == VariantNormal)
4807               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4808                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4809                     basetime, increment);
4810             else
4811               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4812                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4813                     basetime, increment, (int) gameInfo.variant);
4814         } else {
4815             if(gameInfo.variant == VariantNormal)
4816               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4817                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4818                     basetime, increment);
4819             else
4820               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4821                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4822                     basetime, increment, VariantName(gameInfo.variant));
4823         }
4824         DisplayTitle(str);
4825   if (appData.debugMode) {
4826     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4827   }
4828     }
4829
4830
4831     /* Display the board */
4832     if (!pausing && !appData.noGUI) {
4833
4834       if (appData.premove)
4835           if (!gotPremove ||
4836              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4837              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4838               ClearPremoveHighlights();
4839
4840       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4841         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4842       DrawPosition(j, boards[currentMove]);
4843
4844       DisplayMove(moveNum - 1);
4845       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4846             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4847               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4848         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4849       }
4850     }
4851
4852     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4853 #if ZIPPY
4854     if(bookHit) { // [HGM] book: simulate book reply
4855         static char bookMove[MSG_SIZ]; // a bit generous?
4856
4857         programStats.nodes = programStats.depth = programStats.time =
4858         programStats.score = programStats.got_only_move = 0;
4859         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4860
4861         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4862         strcat(bookMove, bookHit);
4863         HandleMachineMove(bookMove, &first);
4864     }
4865 #endif
4866 }
4867
4868 void
4869 GetMoveListEvent ()
4870 {
4871     char buf[MSG_SIZ];
4872     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4873         ics_getting_history = H_REQUESTED;
4874         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4875         SendToICS(buf);
4876     }
4877 }
4878
4879 void
4880 AnalysisPeriodicEvent (int force)
4881 {
4882     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4883          && !force) || !appData.periodicUpdates)
4884       return;
4885
4886     /* Send . command to Crafty to collect stats */
4887     SendToProgram(".\n", &first);
4888
4889     /* Don't send another until we get a response (this makes
4890        us stop sending to old Crafty's which don't understand
4891        the "." command (sending illegal cmds resets node count & time,
4892        which looks bad)) */
4893     programStats.ok_to_send = 0;
4894 }
4895
4896 void
4897 ics_update_width (int new_width)
4898 {
4899         ics_printf("set width %d\n", new_width);
4900 }
4901
4902 void
4903 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4904 {
4905     char buf[MSG_SIZ];
4906
4907     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4908         // null move in variant where engine does not understand it (for analysis purposes)
4909         SendBoard(cps, moveNum + 1); // send position after move in stead.
4910         return;
4911     }
4912     if (cps->useUsermove) {
4913       SendToProgram("usermove ", cps);
4914     }
4915     if (cps->useSAN) {
4916       char *space;
4917       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4918         int len = space - parseList[moveNum];
4919         memcpy(buf, parseList[moveNum], len);
4920         buf[len++] = '\n';
4921         buf[len] = NULLCHAR;
4922       } else {
4923         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4924       }
4925       SendToProgram(buf, cps);
4926     } else {
4927       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4928         AlphaRank(moveList[moveNum], 4);
4929         SendToProgram(moveList[moveNum], cps);
4930         AlphaRank(moveList[moveNum], 4); // and back
4931       } else
4932       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4933        * the engine. It would be nice to have a better way to identify castle
4934        * moves here. */
4935       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4936                                                                          && cps->useOOCastle) {
4937         int fromX = moveList[moveNum][0] - AAA;
4938         int fromY = moveList[moveNum][1] - ONE;
4939         int toX = moveList[moveNum][2] - AAA;
4940         int toY = moveList[moveNum][3] - ONE;
4941         if((boards[moveNum][fromY][fromX] == WhiteKing
4942             && boards[moveNum][toY][toX] == WhiteRook)
4943            || (boards[moveNum][fromY][fromX] == BlackKing
4944                && boards[moveNum][toY][toX] == BlackRook)) {
4945           if(toX > fromX) SendToProgram("O-O\n", cps);
4946           else SendToProgram("O-O-O\n", cps);
4947         }
4948         else SendToProgram(moveList[moveNum], cps);
4949       } else
4950       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4951         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4952           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4953           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4954                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4955         } else
4956           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4957                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4958         SendToProgram(buf, cps);
4959       }
4960       else SendToProgram(moveList[moveNum], cps);
4961       /* End of additions by Tord */
4962     }
4963
4964     /* [HGM] setting up the opening has brought engine in force mode! */
4965     /*       Send 'go' if we are in a mode where machine should play. */
4966     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4967         (gameMode == TwoMachinesPlay   ||
4968 #if ZIPPY
4969          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4970 #endif
4971          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4972         SendToProgram("go\n", cps);
4973   if (appData.debugMode) {
4974     fprintf(debugFP, "(extra)\n");
4975   }
4976     }
4977     setboardSpoiledMachineBlack = 0;
4978 }
4979
4980 void
4981 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4982 {
4983     char user_move[MSG_SIZ];
4984     char suffix[4];
4985
4986     if(gameInfo.variant == VariantSChess && promoChar) {
4987         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4988         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4989     } else suffix[0] = NULLCHAR;
4990
4991     switch (moveType) {
4992       default:
4993         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4994                 (int)moveType, fromX, fromY, toX, toY);
4995         DisplayError(user_move + strlen("say "), 0);
4996         break;
4997       case WhiteKingSideCastle:
4998       case BlackKingSideCastle:
4999       case WhiteQueenSideCastleWild:
5000       case BlackQueenSideCastleWild:
5001       /* PUSH Fabien */
5002       case WhiteHSideCastleFR:
5003       case BlackHSideCastleFR:
5004       /* POP Fabien */
5005         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5006         break;
5007       case WhiteQueenSideCastle:
5008       case BlackQueenSideCastle:
5009       case WhiteKingSideCastleWild:
5010       case BlackKingSideCastleWild:
5011       /* PUSH Fabien */
5012       case WhiteASideCastleFR:
5013       case BlackASideCastleFR:
5014       /* POP Fabien */
5015         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5016         break;
5017       case WhiteNonPromotion:
5018       case BlackNonPromotion:
5019         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5020         break;
5021       case WhitePromotion:
5022       case BlackPromotion:
5023         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5024           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5025                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5026                 PieceToChar(WhiteFerz));
5027         else if(gameInfo.variant == VariantGreat)
5028           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5029                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5030                 PieceToChar(WhiteMan));
5031         else
5032           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5033                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5034                 promoChar);
5035         break;
5036       case WhiteDrop:
5037       case BlackDrop:
5038       drop:
5039         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5040                  ToUpper(PieceToChar((ChessSquare) fromX)),
5041                  AAA + toX, ONE + toY);
5042         break;
5043       case IllegalMove:  /* could be a variant we don't quite understand */
5044         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5045       case NormalMove:
5046       case WhiteCapturesEnPassant:
5047       case BlackCapturesEnPassant:
5048         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5049                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5050         break;
5051     }
5052     SendToICS(user_move);
5053     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5054         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5055 }
5056
5057 void
5058 UploadGameEvent ()
5059 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5060     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5061     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5062     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5063       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5064       return;
5065     }
5066     if(gameMode != IcsExamining) { // is this ever not the case?
5067         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5068
5069         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5070           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5071         } else { // on FICS we must first go to general examine mode
5072           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5073         }
5074         if(gameInfo.variant != VariantNormal) {
5075             // try figure out wild number, as xboard names are not always valid on ICS
5076             for(i=1; i<=36; i++) {
5077               snprintf(buf, MSG_SIZ, "wild/%d", i);
5078                 if(StringToVariant(buf) == gameInfo.variant) break;
5079             }
5080             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5081             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5082             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5083         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5084         SendToICS(ics_prefix);
5085         SendToICS(buf);
5086         if(startedFromSetupPosition || backwardMostMove != 0) {
5087           fen = PositionToFEN(backwardMostMove, NULL);
5088           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5089             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5090             SendToICS(buf);
5091           } else { // FICS: everything has to set by separate bsetup commands
5092             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5093             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5094             SendToICS(buf);
5095             if(!WhiteOnMove(backwardMostMove)) {
5096                 SendToICS("bsetup tomove black\n");
5097             }
5098             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5099             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5100             SendToICS(buf);
5101             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5102             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5103             SendToICS(buf);
5104             i = boards[backwardMostMove][EP_STATUS];
5105             if(i >= 0) { // set e.p.
5106               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5107                 SendToICS(buf);
5108             }
5109             bsetup++;
5110           }
5111         }
5112       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5113             SendToICS("bsetup done\n"); // switch to normal examining.
5114     }
5115     for(i = backwardMostMove; i<last; i++) {
5116         char buf[20];
5117         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5118         SendToICS(buf);
5119     }
5120     SendToICS(ics_prefix);
5121     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5122 }
5123
5124 void
5125 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5126 {
5127     if (rf == DROP_RANK) {
5128       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5129       sprintf(move, "%c@%c%c\n",
5130                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5131     } else {
5132         if (promoChar == 'x' || promoChar == NULLCHAR) {
5133           sprintf(move, "%c%c%c%c\n",
5134                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5135         } else {
5136             sprintf(move, "%c%c%c%c%c\n",
5137                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5138         }
5139     }
5140 }
5141
5142 void
5143 ProcessICSInitScript (FILE *f)
5144 {
5145     char buf[MSG_SIZ];
5146
5147     while (fgets(buf, MSG_SIZ, f)) {
5148         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5149     }
5150
5151     fclose(f);
5152 }
5153
5154
5155 static int lastX, lastY, selectFlag, dragging;
5156
5157 void
5158 Sweep (int step)
5159 {
5160     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5161     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5162     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5163     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5164     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5165     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5166     do {
5167         promoSweep -= step;
5168         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5169         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5170         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5171         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5172         if(!step) step = -1;
5173     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5174             appData.testLegality && (promoSweep == king ||
5175             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5176     ChangeDragPiece(promoSweep);
5177 }
5178
5179 int
5180 PromoScroll (int x, int y)
5181 {
5182   int step = 0;
5183
5184   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5185   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5186   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5187   if(!step) return FALSE;
5188   lastX = x; lastY = y;
5189   if((promoSweep < BlackPawn) == flipView) step = -step;
5190   if(step > 0) selectFlag = 1;
5191   if(!selectFlag) Sweep(step);
5192   return FALSE;
5193 }
5194
5195 void
5196 NextPiece (int step)
5197 {
5198     ChessSquare piece = boards[currentMove][toY][toX];
5199     do {
5200         pieceSweep -= step;
5201         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5202         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5203         if(!step) step = -1;
5204     } while(PieceToChar(pieceSweep) == '.');
5205     boards[currentMove][toY][toX] = pieceSweep;
5206     DrawPosition(FALSE, boards[currentMove]);
5207     boards[currentMove][toY][toX] = piece;
5208 }
5209 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5210 void
5211 AlphaRank (char *move, int n)
5212 {
5213 //    char *p = move, c; int x, y;
5214
5215     if (appData.debugMode) {
5216         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5217     }
5218
5219     if(move[1]=='*' &&
5220        move[2]>='0' && move[2]<='9' &&
5221        move[3]>='a' && move[3]<='x'    ) {
5222         move[1] = '@';
5223         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5224         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5225     } else
5226     if(move[0]>='0' && move[0]<='9' &&
5227        move[1]>='a' && move[1]<='x' &&
5228        move[2]>='0' && move[2]<='9' &&
5229        move[3]>='a' && move[3]<='x'    ) {
5230         /* input move, Shogi -> normal */
5231         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5232         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5233         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5234         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5235     } else
5236     if(move[1]=='@' &&
5237        move[3]>='0' && move[3]<='9' &&
5238        move[2]>='a' && move[2]<='x'    ) {
5239         move[1] = '*';
5240         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5241         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5242     } else
5243     if(
5244        move[0]>='a' && move[0]<='x' &&
5245        move[3]>='0' && move[3]<='9' &&
5246        move[2]>='a' && move[2]<='x'    ) {
5247          /* output move, normal -> Shogi */
5248         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5249         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5250         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5251         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5252         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5253     }
5254     if (appData.debugMode) {
5255         fprintf(debugFP, "   out = '%s'\n", move);
5256     }
5257 }
5258
5259 char yy_textstr[8000];
5260
5261 /* Parser for moves from gnuchess, ICS, or user typein box */
5262 Boolean
5263 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5264 {
5265     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5266
5267     switch (*moveType) {
5268       case WhitePromotion:
5269       case BlackPromotion:
5270       case WhiteNonPromotion:
5271       case BlackNonPromotion:
5272       case NormalMove:
5273       case WhiteCapturesEnPassant:
5274       case BlackCapturesEnPassant:
5275       case WhiteKingSideCastle:
5276       case WhiteQueenSideCastle:
5277       case BlackKingSideCastle:
5278       case BlackQueenSideCastle:
5279       case WhiteKingSideCastleWild:
5280       case WhiteQueenSideCastleWild:
5281       case BlackKingSideCastleWild:
5282       case BlackQueenSideCastleWild:
5283       /* Code added by Tord: */
5284       case WhiteHSideCastleFR:
5285       case WhiteASideCastleFR:
5286       case BlackHSideCastleFR:
5287       case BlackASideCastleFR:
5288       /* End of code added by Tord */
5289       case IllegalMove:         /* bug or odd chess variant */
5290         *fromX = currentMoveString[0] - AAA;
5291         *fromY = currentMoveString[1] - ONE;
5292         *toX = currentMoveString[2] - AAA;
5293         *toY = currentMoveString[3] - ONE;
5294         *promoChar = currentMoveString[4];
5295         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5296             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5297     if (appData.debugMode) {
5298         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5299     }
5300             *fromX = *fromY = *toX = *toY = 0;
5301             return FALSE;
5302         }
5303         if (appData.testLegality) {
5304           return (*moveType != IllegalMove);
5305         } else {
5306           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5307                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5308         }
5309
5310       case WhiteDrop:
5311       case BlackDrop:
5312         *fromX = *moveType == WhiteDrop ?
5313           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5314           (int) CharToPiece(ToLower(currentMoveString[0]));
5315         *fromY = DROP_RANK;
5316         *toX = currentMoveString[2] - AAA;
5317         *toY = currentMoveString[3] - ONE;
5318         *promoChar = NULLCHAR;
5319         return TRUE;
5320
5321       case AmbiguousMove:
5322       case ImpossibleMove:
5323       case EndOfFile:
5324       case ElapsedTime:
5325       case Comment:
5326       case PGNTag:
5327       case NAG:
5328       case WhiteWins:
5329       case BlackWins:
5330       case GameIsDrawn:
5331       default:
5332     if (appData.debugMode) {
5333         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5334     }
5335         /* bug? */
5336         *fromX = *fromY = *toX = *toY = 0;
5337         *promoChar = NULLCHAR;
5338         return FALSE;
5339     }
5340 }
5341
5342 Boolean pushed = FALSE;
5343 char *lastParseAttempt;
5344
5345 void
5346 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5347 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5348   int fromX, fromY, toX, toY; char promoChar;
5349   ChessMove moveType;
5350   Boolean valid;
5351   int nr = 0;
5352
5353   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5354     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5355     pushed = TRUE;
5356   }
5357   endPV = forwardMostMove;
5358   do {
5359     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5360     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5361     lastParseAttempt = pv;
5362     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5363     if(!valid && nr == 0 &&
5364        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5365         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5366         // Hande case where played move is different from leading PV move
5367         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5368         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5369         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5370         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5371           endPV += 2; // if position different, keep this
5372           moveList[endPV-1][0] = fromX + AAA;
5373           moveList[endPV-1][1] = fromY + ONE;
5374           moveList[endPV-1][2] = toX + AAA;
5375           moveList[endPV-1][3] = toY + ONE;
5376           parseList[endPV-1][0] = NULLCHAR;
5377           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5378         }
5379       }
5380     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5381     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5382     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5383     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5384         valid++; // allow comments in PV
5385         continue;
5386     }
5387     nr++;
5388     if(endPV+1 > framePtr) break; // no space, truncate
5389     if(!valid) break;
5390     endPV++;
5391     CopyBoard(boards[endPV], boards[endPV-1]);
5392     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5393     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5394     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5395     CoordsToAlgebraic(boards[endPV - 1],
5396                              PosFlags(endPV - 1),
5397                              fromY, fromX, toY, toX, promoChar,
5398                              parseList[endPV - 1]);
5399   } while(valid);
5400   if(atEnd == 2) return; // used hidden, for PV conversion
5401   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5402   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5403   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5404                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5405   DrawPosition(TRUE, boards[currentMove]);
5406 }
5407
5408 int
5409 MultiPV (ChessProgramState *cps)
5410 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5411         int i;
5412         for(i=0; i<cps->nrOptions; i++)
5413             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5414                 return i;
5415         return -1;
5416 }
5417
5418 Boolean
5419 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5420 {
5421         int startPV, multi, lineStart, origIndex = index;
5422         char *p, buf2[MSG_SIZ];
5423
5424         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5425         lastX = x; lastY = y;
5426         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5427         lineStart = startPV = index;
5428         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5429         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5430         index = startPV;
5431         do{ while(buf[index] && buf[index] != '\n') index++;
5432         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5433         buf[index] = 0;
5434         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5435                 int n = first.option[multi].value;
5436                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5437                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5438                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5439                 first.option[multi].value = n;
5440                 *start = *end = 0;
5441                 return FALSE;
5442         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5443                 ExcludeClick(origIndex - lineStart);
5444                 return FALSE;
5445         }
5446         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5447         *start = startPV; *end = index-1;
5448         return TRUE;
5449 }
5450
5451 char *
5452 PvToSAN (char *pv)
5453 {
5454         static char buf[10*MSG_SIZ];
5455         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5456         *buf = NULLCHAR;
5457         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5458         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5459         for(i = forwardMostMove; i<endPV; i++){
5460             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5461             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5462             k += strlen(buf+k);
5463         }
5464         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5465         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5466         endPV = savedEnd;
5467         return buf;
5468 }
5469
5470 Boolean
5471 LoadPV (int x, int y)
5472 { // called on right mouse click to load PV
5473   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5474   lastX = x; lastY = y;
5475   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5476   return TRUE;
5477 }
5478
5479 void
5480 UnLoadPV ()
5481 {
5482   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5483   if(endPV < 0) return;
5484   if(appData.autoCopyPV) CopyFENToClipboard();
5485   endPV = -1;
5486   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5487         Boolean saveAnimate = appData.animate;
5488         if(pushed) {
5489             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5490                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5491             } else storedGames--; // abandon shelved tail of original game
5492         }
5493         pushed = FALSE;
5494         forwardMostMove = currentMove;
5495         currentMove = oldFMM;
5496         appData.animate = FALSE;
5497         ToNrEvent(forwardMostMove);
5498         appData.animate = saveAnimate;
5499   }
5500   currentMove = forwardMostMove;
5501   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5502   ClearPremoveHighlights();
5503   DrawPosition(TRUE, boards[currentMove]);
5504 }
5505
5506 void
5507 MovePV (int x, int y, int h)
5508 { // step through PV based on mouse coordinates (called on mouse move)
5509   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5510
5511   // we must somehow check if right button is still down (might be released off board!)
5512   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5513   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5514   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5515   if(!step) return;
5516   lastX = x; lastY = y;
5517
5518   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5519   if(endPV < 0) return;
5520   if(y < margin) step = 1; else
5521   if(y > h - margin) step = -1;
5522   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5523   currentMove += step;
5524   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5525   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5526                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5527   DrawPosition(FALSE, boards[currentMove]);
5528 }
5529
5530
5531 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5532 // All positions will have equal probability, but the current method will not provide a unique
5533 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5534 #define DARK 1
5535 #define LITE 2
5536 #define ANY 3
5537
5538 int squaresLeft[4];
5539 int piecesLeft[(int)BlackPawn];
5540 int seed, nrOfShuffles;
5541
5542 void
5543 GetPositionNumber ()
5544 {       // sets global variable seed
5545         int i;
5546
5547         seed = appData.defaultFrcPosition;
5548         if(seed < 0) { // randomize based on time for negative FRC position numbers
5549                 for(i=0; i<50; i++) seed += random();
5550                 seed = random() ^ random() >> 8 ^ random() << 8;
5551                 if(seed<0) seed = -seed;
5552         }
5553 }
5554
5555 int
5556 put (Board board, int pieceType, int rank, int n, int shade)
5557 // put the piece on the (n-1)-th empty squares of the given shade
5558 {
5559         int i;
5560
5561         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5562                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5563                         board[rank][i] = (ChessSquare) pieceType;
5564                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5565                         squaresLeft[ANY]--;
5566                         piecesLeft[pieceType]--;
5567                         return i;
5568                 }
5569         }
5570         return -1;
5571 }
5572
5573
5574 void
5575 AddOnePiece (Board board, int pieceType, int rank, int shade)
5576 // calculate where the next piece goes, (any empty square), and put it there
5577 {
5578         int i;
5579
5580         i = seed % squaresLeft[shade];
5581         nrOfShuffles *= squaresLeft[shade];
5582         seed /= squaresLeft[shade];
5583         put(board, pieceType, rank, i, shade);
5584 }
5585
5586 void
5587 AddTwoPieces (Board board, int pieceType, int rank)
5588 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5589 {
5590         int i, n=squaresLeft[ANY], j=n-1, k;
5591
5592         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5593         i = seed % k;  // pick one
5594         nrOfShuffles *= k;
5595         seed /= k;
5596         while(i >= j) i -= j--;
5597         j = n - 1 - j; i += j;
5598         put(board, pieceType, rank, j, ANY);
5599         put(board, pieceType, rank, i, ANY);
5600 }
5601
5602 void
5603 SetUpShuffle (Board board, int number)
5604 {
5605         int i, p, first=1;
5606
5607         GetPositionNumber(); nrOfShuffles = 1;
5608
5609         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5610         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5611         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5612
5613         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5614
5615         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5616             p = (int) board[0][i];
5617             if(p < (int) BlackPawn) piecesLeft[p] ++;
5618             board[0][i] = EmptySquare;
5619         }
5620
5621         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5622             // shuffles restricted to allow normal castling put KRR first
5623             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5624                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5625             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5626                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5627             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5628                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5629             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5630                 put(board, WhiteRook, 0, 0, ANY);
5631             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5632         }
5633
5634         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5635             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5636             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5637                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5638                 while(piecesLeft[p] >= 2) {
5639                     AddOnePiece(board, p, 0, LITE);
5640                     AddOnePiece(board, p, 0, DARK);
5641                 }
5642                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5643             }
5644
5645         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5646             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5647             // but we leave King and Rooks for last, to possibly obey FRC restriction
5648             if(p == (int)WhiteRook) continue;
5649             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5650             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5651         }
5652
5653         // now everything is placed, except perhaps King (Unicorn) and Rooks
5654
5655         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5656             // Last King gets castling rights
5657             while(piecesLeft[(int)WhiteUnicorn]) {
5658                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5659                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5660             }
5661
5662             while(piecesLeft[(int)WhiteKing]) {
5663                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5664                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5665             }
5666
5667
5668         } else {
5669             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5670             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5671         }
5672
5673         // Only Rooks can be left; simply place them all
5674         while(piecesLeft[(int)WhiteRook]) {
5675                 i = put(board, WhiteRook, 0, 0, ANY);
5676                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5677                         if(first) {
5678                                 first=0;
5679                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5680                         }
5681                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5682                 }
5683         }
5684         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5685             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5686         }
5687
5688         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5689 }
5690
5691 int
5692 SetCharTable (char *table, const char * map)
5693 /* [HGM] moved here from winboard.c because of its general usefulness */
5694 /*       Basically a safe strcpy that uses the last character as King */
5695 {
5696     int result = FALSE; int NrPieces;
5697
5698     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5699                     && NrPieces >= 12 && !(NrPieces&1)) {
5700         int i; /* [HGM] Accept even length from 12 to 34 */
5701
5702         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5703         for( i=0; i<NrPieces/2-1; i++ ) {
5704             table[i] = map[i];
5705             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5706         }
5707         table[(int) WhiteKing]  = map[NrPieces/2-1];
5708         table[(int) BlackKing]  = map[NrPieces-1];
5709
5710         result = TRUE;
5711     }
5712
5713     return result;
5714 }
5715
5716 void
5717 Prelude (Board board)
5718 {       // [HGM] superchess: random selection of exo-pieces
5719         int i, j, k; ChessSquare p;
5720         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5721
5722         GetPositionNumber(); // use FRC position number
5723
5724         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5725             SetCharTable(pieceToChar, appData.pieceToCharTable);
5726             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5727                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5728         }
5729
5730         j = seed%4;                 seed /= 4;
5731         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5732         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5733         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5734         j = seed%3 + (seed%3 >= j); seed /= 3;
5735         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5736         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5737         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5738         j = seed%3;                 seed /= 3;
5739         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5740         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5741         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5742         j = seed%2 + (seed%2 >= j); seed /= 2;
5743         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5744         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5745         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5746         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5747         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5748         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5749         put(board, exoPieces[0],    0, 0, ANY);
5750         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5751 }
5752
5753 void
5754 InitPosition (int redraw)
5755 {
5756     ChessSquare (* pieces)[BOARD_FILES];
5757     int i, j, pawnRow, overrule,
5758     oldx = gameInfo.boardWidth,
5759     oldy = gameInfo.boardHeight,
5760     oldh = gameInfo.holdingsWidth;
5761     static int oldv;
5762
5763     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5764
5765     /* [AS] Initialize pv info list [HGM] and game status */
5766     {
5767         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5768             pvInfoList[i].depth = 0;
5769             boards[i][EP_STATUS] = EP_NONE;
5770             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5771         }
5772
5773         initialRulePlies = 0; /* 50-move counter start */
5774
5775         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5776         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5777     }
5778
5779
5780     /* [HGM] logic here is completely changed. In stead of full positions */
5781     /* the initialized data only consist of the two backranks. The switch */
5782     /* selects which one we will use, which is than copied to the Board   */
5783     /* initialPosition, which for the rest is initialized by Pawns and    */
5784     /* empty squares. This initial position is then copied to boards[0],  */
5785     /* possibly after shuffling, so that it remains available.            */
5786
5787     gameInfo.holdingsWidth = 0; /* default board sizes */
5788     gameInfo.boardWidth    = 8;
5789     gameInfo.boardHeight   = 8;
5790     gameInfo.holdingsSize  = 0;
5791     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5792     for(i=0; i<BOARD_FILES-2; i++)
5793       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5794     initialPosition[EP_STATUS] = EP_NONE;
5795     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5796     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5797          SetCharTable(pieceNickName, appData.pieceNickNames);
5798     else SetCharTable(pieceNickName, "............");
5799     pieces = FIDEArray;
5800
5801     switch (gameInfo.variant) {
5802     case VariantFischeRandom:
5803       shuffleOpenings = TRUE;
5804     default:
5805       break;
5806     case VariantShatranj:
5807       pieces = ShatranjArray;
5808       nrCastlingRights = 0;
5809       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5810       break;
5811     case VariantMakruk:
5812       pieces = makrukArray;
5813       nrCastlingRights = 0;
5814       startedFromSetupPosition = TRUE;
5815       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5816       break;
5817     case VariantTwoKings:
5818       pieces = twoKingsArray;
5819       break;
5820     case VariantGrand:
5821       pieces = GrandArray;
5822       nrCastlingRights = 0;
5823       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5824       gameInfo.boardWidth = 10;
5825       gameInfo.boardHeight = 10;
5826       gameInfo.holdingsSize = 7;
5827       break;
5828     case VariantCapaRandom:
5829       shuffleOpenings = TRUE;
5830     case VariantCapablanca:
5831       pieces = CapablancaArray;
5832       gameInfo.boardWidth = 10;
5833       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5834       break;
5835     case VariantGothic:
5836       pieces = GothicArray;
5837       gameInfo.boardWidth = 10;
5838       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5839       break;
5840     case VariantSChess:
5841       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5842       gameInfo.holdingsSize = 7;
5843       break;
5844     case VariantJanus:
5845       pieces = JanusArray;
5846       gameInfo.boardWidth = 10;
5847       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5848       nrCastlingRights = 6;
5849         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5850         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5851         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5852         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5853         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5854         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5855       break;
5856     case VariantFalcon:
5857       pieces = FalconArray;
5858       gameInfo.boardWidth = 10;
5859       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5860       break;
5861     case VariantXiangqi:
5862       pieces = XiangqiArray;
5863       gameInfo.boardWidth  = 9;
5864       gameInfo.boardHeight = 10;
5865       nrCastlingRights = 0;
5866       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5867       break;
5868     case VariantShogi:
5869       pieces = ShogiArray;
5870       gameInfo.boardWidth  = 9;
5871       gameInfo.boardHeight = 9;
5872       gameInfo.holdingsSize = 7;
5873       nrCastlingRights = 0;
5874       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5875       break;
5876     case VariantCourier:
5877       pieces = CourierArray;
5878       gameInfo.boardWidth  = 12;
5879       nrCastlingRights = 0;
5880       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5881       break;
5882     case VariantKnightmate:
5883       pieces = KnightmateArray;
5884       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5885       break;
5886     case VariantSpartan:
5887       pieces = SpartanArray;
5888       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5889       break;
5890     case VariantFairy:
5891       pieces = fairyArray;
5892       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5893       break;
5894     case VariantGreat:
5895       pieces = GreatArray;
5896       gameInfo.boardWidth = 10;
5897       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5898       gameInfo.holdingsSize = 8;
5899       break;
5900     case VariantSuper:
5901       pieces = FIDEArray;
5902       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5903       gameInfo.holdingsSize = 8;
5904       startedFromSetupPosition = TRUE;
5905       break;
5906     case VariantCrazyhouse:
5907     case VariantBughouse:
5908       pieces = FIDEArray;
5909       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5910       gameInfo.holdingsSize = 5;
5911       break;
5912     case VariantWildCastle:
5913       pieces = FIDEArray;
5914       /* !!?shuffle with kings guaranteed to be on d or e file */
5915       shuffleOpenings = 1;
5916       break;
5917     case VariantNoCastle:
5918       pieces = FIDEArray;
5919       nrCastlingRights = 0;
5920       /* !!?unconstrained back-rank shuffle */
5921       shuffleOpenings = 1;
5922       break;
5923     }
5924
5925     overrule = 0;
5926     if(appData.NrFiles >= 0) {
5927         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5928         gameInfo.boardWidth = appData.NrFiles;
5929     }
5930     if(appData.NrRanks >= 0) {
5931         gameInfo.boardHeight = appData.NrRanks;
5932     }
5933     if(appData.holdingsSize >= 0) {
5934         i = appData.holdingsSize;
5935         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5936         gameInfo.holdingsSize = i;
5937     }
5938     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5939     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5940         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5941
5942     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5943     if(pawnRow < 1) pawnRow = 1;
5944     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5945
5946     /* User pieceToChar list overrules defaults */
5947     if(appData.pieceToCharTable != NULL)
5948         SetCharTable(pieceToChar, appData.pieceToCharTable);
5949
5950     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5951
5952         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5953             s = (ChessSquare) 0; /* account holding counts in guard band */
5954         for( i=0; i<BOARD_HEIGHT; i++ )
5955             initialPosition[i][j] = s;
5956
5957         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5958         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5959         initialPosition[pawnRow][j] = WhitePawn;
5960         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5961         if(gameInfo.variant == VariantXiangqi) {
5962             if(j&1) {
5963                 initialPosition[pawnRow][j] =
5964                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5965                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5966                    initialPosition[2][j] = WhiteCannon;
5967                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5968                 }
5969             }
5970         }
5971         if(gameInfo.variant == VariantGrand) {
5972             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5973                initialPosition[0][j] = WhiteRook;
5974                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5975             }
5976         }
5977         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5978     }
5979     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5980
5981             j=BOARD_LEFT+1;
5982             initialPosition[1][j] = WhiteBishop;
5983             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5984             j=BOARD_RGHT-2;
5985             initialPosition[1][j] = WhiteRook;
5986             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5987     }
5988
5989     if( nrCastlingRights == -1) {
5990         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5991         /*       This sets default castling rights from none to normal corners   */
5992         /* Variants with other castling rights must set them themselves above    */
5993         nrCastlingRights = 6;
5994
5995         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5996         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5997         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5998         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5999         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6000         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6001      }
6002
6003      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6004      if(gameInfo.variant == VariantGreat) { // promotion commoners
6005         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6006         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6007         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6008         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6009      }
6010      if( gameInfo.variant == VariantSChess ) {
6011       initialPosition[1][0] = BlackMarshall;
6012       initialPosition[2][0] = BlackAngel;
6013       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6014       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6015       initialPosition[1][1] = initialPosition[2][1] = 
6016       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6017      }
6018   if (appData.debugMode) {
6019     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6020   }
6021     if(shuffleOpenings) {
6022         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6023         startedFromSetupPosition = TRUE;
6024     }
6025     if(startedFromPositionFile) {
6026       /* [HGM] loadPos: use PositionFile for every new game */
6027       CopyBoard(initialPosition, filePosition);
6028       for(i=0; i<nrCastlingRights; i++)
6029           initialRights[i] = filePosition[CASTLING][i];
6030       startedFromSetupPosition = TRUE;
6031     }
6032
6033     CopyBoard(boards[0], initialPosition);
6034
6035     if(oldx != gameInfo.boardWidth ||
6036        oldy != gameInfo.boardHeight ||
6037        oldv != gameInfo.variant ||
6038        oldh != gameInfo.holdingsWidth
6039                                          )
6040             InitDrawingSizes(-2 ,0);
6041
6042     oldv = gameInfo.variant;
6043     if (redraw)
6044       DrawPosition(TRUE, boards[currentMove]);
6045 }
6046
6047 void
6048 SendBoard (ChessProgramState *cps, int moveNum)
6049 {
6050     char message[MSG_SIZ];
6051
6052     if (cps->useSetboard) {
6053       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6054       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6055       SendToProgram(message, cps);
6056       free(fen);
6057
6058     } else {
6059       ChessSquare *bp;
6060       int i, j, left=0, right=BOARD_WIDTH;
6061       /* Kludge to set black to move, avoiding the troublesome and now
6062        * deprecated "black" command.
6063        */
6064       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6065         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6066
6067       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6068
6069       SendToProgram("edit\n", cps);
6070       SendToProgram("#\n", cps);
6071       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6072         bp = &boards[moveNum][i][left];
6073         for (j = left; j < right; j++, bp++) {
6074           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6075           if ((int) *bp < (int) BlackPawn) {
6076             if(j == BOARD_RGHT+1)
6077                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6078             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6079             if(message[0] == '+' || message[0] == '~') {
6080               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6081                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6082                         AAA + j, ONE + i);
6083             }
6084             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6085                 message[1] = BOARD_RGHT   - 1 - j + '1';
6086                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6087             }
6088             SendToProgram(message, cps);
6089           }
6090         }
6091       }
6092
6093       SendToProgram("c\n", cps);
6094       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6095         bp = &boards[moveNum][i][left];
6096         for (j = left; j < right; j++, bp++) {
6097           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6098           if (((int) *bp != (int) EmptySquare)
6099               && ((int) *bp >= (int) BlackPawn)) {
6100             if(j == BOARD_LEFT-2)
6101                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6102             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6103                     AAA + j, ONE + i);
6104             if(message[0] == '+' || message[0] == '~') {
6105               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6106                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6107                         AAA + j, ONE + i);
6108             }
6109             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6110                 message[1] = BOARD_RGHT   - 1 - j + '1';
6111                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6112             }
6113             SendToProgram(message, cps);
6114           }
6115         }
6116       }
6117
6118       SendToProgram(".\n", cps);
6119     }
6120     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6121 }
6122
6123 char exclusionHeader[MSG_SIZ];
6124 int exCnt, excludePtr;
6125 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6126 static Exclusion excluTab[200];
6127 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6128
6129 static void
6130 WriteMap (int s)
6131 {
6132     int j;
6133     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6134     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6135 }
6136
6137 static void
6138 ClearMap ()
6139 {
6140     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6141     excludePtr = 24; exCnt = 0;
6142     WriteMap(0);
6143 }
6144
6145 static void
6146 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6147 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6148     char buf[2*MOVE_LEN], *p;
6149     Exclusion *e = excluTab;
6150     int i;
6151     for(i=0; i<exCnt; i++)
6152         if(e[i].ff == fromX && e[i].fr == fromY &&
6153            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6154     if(i == exCnt) { // was not in exclude list; add it
6155         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6156         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6157             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6158             return; // abort
6159         }
6160         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6161         excludePtr++; e[i].mark = excludePtr++;
6162         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6163         exCnt++;
6164     }
6165     exclusionHeader[e[i].mark] = state;
6166 }
6167
6168 static int
6169 ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, char state)
6170 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6171     char buf[MSG_SIZ];
6172     int j, k;
6173     ChessMove moveType;
6174     if(promoChar == -1) { // kludge to indicate best move
6175         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6176             return 1; // if unparsable, abort
6177     }
6178     // update exclusion map (resolving toggle by consulting existing state)
6179     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6180     j = k%8; k >>= 3;
6181     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6182     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6183          excludeMap[k] |=   1<<j;
6184     else excludeMap[k] &= ~(1<<j);
6185     // update header
6186     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6187     // inform engine
6188     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6189     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6190     SendToProgram(buf, &first);
6191     return (state == '+');
6192 }
6193
6194 static void
6195 ExcludeClick (int index)
6196 {
6197     int i, j;
6198     Exclusion *e = excluTab;
6199     if(index < 25) { // none, best or tail clicked
6200         if(index < 13) { // none: include all
6201             WriteMap(0); // clear map
6202             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6203             SendToProgram("include all\n", &first); // and inform engine
6204         } else if(index > 18) { // tail
6205             if(exclusionHeader[19] == '-') { // tail was excluded
6206                 SendToProgram("include all\n", &first);
6207                 WriteMap(0); // clear map completely
6208                 // now re-exclude selected moves
6209                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6210                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6211             } else { // tail was included or in mixed state
6212                 SendToProgram("exclude all\n", &first);
6213                 WriteMap(0xFF); // fill map completely
6214                 // now re-include selected moves
6215                 j = 0; // count them
6216                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6217                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6218                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6219             }
6220         } else { // best
6221             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6222         }
6223     } else {
6224         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6225             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6226             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6227             break;
6228         }
6229     }
6230 }
6231
6232 ChessSquare
6233 DefaultPromoChoice (int white)
6234 {
6235     ChessSquare result;
6236     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6237         result = WhiteFerz; // no choice
6238     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6239         result= WhiteKing; // in Suicide Q is the last thing we want
6240     else if(gameInfo.variant == VariantSpartan)
6241         result = white ? WhiteQueen : WhiteAngel;
6242     else result = WhiteQueen;
6243     if(!white) result = WHITE_TO_BLACK result;
6244     return result;
6245 }
6246
6247 static int autoQueen; // [HGM] oneclick
6248
6249 int
6250 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6251 {
6252     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6253     /* [HGM] add Shogi promotions */
6254     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6255     ChessSquare piece;
6256     ChessMove moveType;
6257     Boolean premove;
6258
6259     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6260     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6261
6262     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6263       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6264         return FALSE;
6265
6266     piece = boards[currentMove][fromY][fromX];
6267     if(gameInfo.variant == VariantShogi) {
6268         promotionZoneSize = BOARD_HEIGHT/3;
6269         highestPromotingPiece = (int)WhiteFerz;
6270     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6271         promotionZoneSize = 3;
6272     }
6273
6274     // Treat Lance as Pawn when it is not representing Amazon
6275     if(gameInfo.variant != VariantSuper) {
6276         if(piece == WhiteLance) piece = WhitePawn; else
6277         if(piece == BlackLance) piece = BlackPawn;
6278     }
6279
6280     // next weed out all moves that do not touch the promotion zone at all
6281     if((int)piece >= BlackPawn) {
6282         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6283              return FALSE;
6284         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6285     } else {
6286         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6287            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6288     }
6289
6290     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6291
6292     // weed out mandatory Shogi promotions
6293     if(gameInfo.variant == VariantShogi) {
6294         if(piece >= BlackPawn) {
6295             if(toY == 0 && piece == BlackPawn ||
6296                toY == 0 && piece == BlackQueen ||
6297                toY <= 1 && piece == BlackKnight) {
6298                 *promoChoice = '+';
6299                 return FALSE;
6300             }
6301         } else {
6302             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6303                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6304                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6305                 *promoChoice = '+';
6306                 return FALSE;
6307             }
6308         }
6309     }
6310
6311     // weed out obviously illegal Pawn moves
6312     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6313         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6314         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6315         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6316         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6317         // note we are not allowed to test for valid (non-)capture, due to premove
6318     }
6319
6320     // we either have a choice what to promote to, or (in Shogi) whether to promote
6321     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6322         *promoChoice = PieceToChar(BlackFerz);  // no choice
6323         return FALSE;
6324     }
6325     // no sense asking what we must promote to if it is going to explode...
6326     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6327         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6328         return FALSE;
6329     }
6330     // give caller the default choice even if we will not make it
6331     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6332     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6333     if(        sweepSelect && gameInfo.variant != VariantGreat
6334                            && gameInfo.variant != VariantGrand
6335                            && gameInfo.variant != VariantSuper) return FALSE;
6336     if(autoQueen) return FALSE; // predetermined
6337
6338     // suppress promotion popup on illegal moves that are not premoves
6339     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6340               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6341     if(appData.testLegality && !premove) {
6342         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6343                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6344         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6345             return FALSE;
6346     }
6347
6348     return TRUE;
6349 }
6350
6351 int
6352 InPalace (int row, int column)
6353 {   /* [HGM] for Xiangqi */
6354     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6355          column < (BOARD_WIDTH + 4)/2 &&
6356          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6357     return FALSE;
6358 }
6359
6360 int
6361 PieceForSquare (int x, int y)
6362 {
6363   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6364      return -1;
6365   else
6366      return boards[currentMove][y][x];
6367 }
6368
6369 int
6370 OKToStartUserMove (int x, int y)
6371 {
6372     ChessSquare from_piece;
6373     int white_piece;
6374
6375     if (matchMode) return FALSE;
6376     if (gameMode == EditPosition) return TRUE;
6377
6378     if (x >= 0 && y >= 0)
6379       from_piece = boards[currentMove][y][x];
6380     else
6381       from_piece = EmptySquare;
6382
6383     if (from_piece == EmptySquare) return FALSE;
6384
6385     white_piece = (int)from_piece >= (int)WhitePawn &&
6386       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6387
6388     switch (gameMode) {
6389       case AnalyzeFile:
6390       case TwoMachinesPlay:
6391       case EndOfGame:
6392         return FALSE;
6393
6394       case IcsObserving:
6395       case IcsIdle:
6396         return FALSE;
6397
6398       case MachinePlaysWhite:
6399       case IcsPlayingBlack:
6400         if (appData.zippyPlay) return FALSE;
6401         if (white_piece) {
6402             DisplayMoveError(_("You are playing Black"));
6403             return FALSE;
6404         }
6405         break;
6406
6407       case MachinePlaysBlack:
6408       case IcsPlayingWhite:
6409         if (appData.zippyPlay) return FALSE;
6410         if (!white_piece) {
6411             DisplayMoveError(_("You are playing White"));
6412             return FALSE;
6413         }
6414         break;
6415
6416       case PlayFromGameFile:
6417             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6418       case EditGame:
6419         if (!white_piece && WhiteOnMove(currentMove)) {
6420             DisplayMoveError(_("It is White's turn"));
6421             return FALSE;
6422         }
6423         if (white_piece && !WhiteOnMove(currentMove)) {
6424             DisplayMoveError(_("It is Black's turn"));
6425             return FALSE;
6426         }
6427         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6428             /* Editing correspondence game history */
6429             /* Could disallow this or prompt for confirmation */
6430             cmailOldMove = -1;
6431         }
6432         break;
6433
6434       case BeginningOfGame:
6435         if (appData.icsActive) return FALSE;
6436         if (!appData.noChessProgram) {
6437             if (!white_piece) {
6438                 DisplayMoveError(_("You are playing White"));
6439                 return FALSE;
6440             }
6441         }
6442         break;
6443
6444       case Training:
6445         if (!white_piece && WhiteOnMove(currentMove)) {
6446             DisplayMoveError(_("It is White's turn"));
6447             return FALSE;
6448         }
6449         if (white_piece && !WhiteOnMove(currentMove)) {
6450             DisplayMoveError(_("It is Black's turn"));
6451             return FALSE;
6452         }
6453         break;
6454
6455       default:
6456       case IcsExamining:
6457         break;
6458     }
6459     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6460         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6461         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6462         && gameMode != AnalyzeFile && gameMode != Training) {
6463         DisplayMoveError(_("Displayed position is not current"));
6464         return FALSE;
6465     }
6466     return TRUE;
6467 }
6468
6469 Boolean
6470 OnlyMove (int *x, int *y, Boolean captures) 
6471 {
6472     DisambiguateClosure cl;
6473     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6474     switch(gameMode) {
6475       case MachinePlaysBlack:
6476       case IcsPlayingWhite:
6477       case BeginningOfGame:
6478         if(!WhiteOnMove(currentMove)) return FALSE;
6479         break;
6480       case MachinePlaysWhite:
6481       case IcsPlayingBlack:
6482         if(WhiteOnMove(currentMove)) return FALSE;
6483         break;
6484       case EditGame:
6485         break;
6486       default:
6487         return FALSE;
6488     }
6489     cl.pieceIn = EmptySquare;
6490     cl.rfIn = *y;
6491     cl.ffIn = *x;
6492     cl.rtIn = -1;
6493     cl.ftIn = -1;
6494     cl.promoCharIn = NULLCHAR;
6495     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6496     if( cl.kind == NormalMove ||
6497         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6498         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6499         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6500       fromX = cl.ff;
6501       fromY = cl.rf;
6502       *x = cl.ft;
6503       *y = cl.rt;
6504       return TRUE;
6505     }
6506     if(cl.kind != ImpossibleMove) return FALSE;
6507     cl.pieceIn = EmptySquare;
6508     cl.rfIn = -1;
6509     cl.ffIn = -1;
6510     cl.rtIn = *y;
6511     cl.ftIn = *x;
6512     cl.promoCharIn = NULLCHAR;
6513     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6514     if( cl.kind == NormalMove ||
6515         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6516         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6517         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6518       fromX = cl.ff;
6519       fromY = cl.rf;
6520       *x = cl.ft;
6521       *y = cl.rt;
6522       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6523       return TRUE;
6524     }
6525     return FALSE;
6526 }
6527
6528 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6529 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6530 int lastLoadGameUseList = FALSE;
6531 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6532 ChessMove lastLoadGameStart = EndOfFile;
6533 int doubleClick;
6534
6535 void
6536 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6537 {
6538     ChessMove moveType;
6539     ChessSquare pdown, pup;
6540     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6541
6542
6543     /* Check if the user is playing in turn.  This is complicated because we
6544        let the user "pick up" a piece before it is his turn.  So the piece he
6545        tried to pick up may have been captured by the time he puts it down!
6546        Therefore we use the color the user is supposed to be playing in this
6547        test, not the color of the piece that is currently on the starting
6548        square---except in EditGame mode, where the user is playing both
6549        sides; fortunately there the capture race can't happen.  (It can
6550        now happen in IcsExamining mode, but that's just too bad.  The user
6551        will get a somewhat confusing message in that case.)
6552        */
6553
6554     switch (gameMode) {
6555       case AnalyzeFile:
6556       case TwoMachinesPlay:
6557       case EndOfGame:
6558       case IcsObserving:
6559       case IcsIdle:
6560         /* We switched into a game mode where moves are not accepted,
6561            perhaps while the mouse button was down. */
6562         return;
6563
6564       case MachinePlaysWhite:
6565         /* User is moving for Black */
6566         if (WhiteOnMove(currentMove)) {
6567             DisplayMoveError(_("It is White's turn"));
6568             return;
6569         }
6570         break;
6571
6572       case MachinePlaysBlack:
6573         /* User is moving for White */
6574         if (!WhiteOnMove(currentMove)) {
6575             DisplayMoveError(_("It is Black's turn"));
6576             return;
6577         }
6578         break;
6579
6580       case PlayFromGameFile:
6581             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6582       case EditGame:
6583       case IcsExamining:
6584       case BeginningOfGame:
6585       case AnalyzeMode:
6586       case Training:
6587         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6588         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6589             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6590             /* User is moving for Black */
6591             if (WhiteOnMove(currentMove)) {
6592                 DisplayMoveError(_("It is White's turn"));
6593                 return;
6594             }
6595         } else {
6596             /* User is moving for White */
6597             if (!WhiteOnMove(currentMove)) {
6598                 DisplayMoveError(_("It is Black's turn"));
6599                 return;
6600             }
6601         }
6602         break;
6603
6604       case IcsPlayingBlack:
6605         /* User is moving for Black */
6606         if (WhiteOnMove(currentMove)) {
6607             if (!appData.premove) {
6608                 DisplayMoveError(_("It is White's turn"));
6609             } else if (toX >= 0 && toY >= 0) {
6610                 premoveToX = toX;
6611                 premoveToY = toY;
6612                 premoveFromX = fromX;
6613                 premoveFromY = fromY;
6614                 premovePromoChar = promoChar;
6615                 gotPremove = 1;
6616                 if (appData.debugMode)
6617                     fprintf(debugFP, "Got premove: fromX %d,"
6618                             "fromY %d, toX %d, toY %d\n",
6619                             fromX, fromY, toX, toY);
6620             }
6621             return;
6622         }
6623         break;
6624
6625       case IcsPlayingWhite:
6626         /* User is moving for White */
6627         if (!WhiteOnMove(currentMove)) {
6628             if (!appData.premove) {
6629                 DisplayMoveError(_("It is Black's turn"));
6630             } else if (toX >= 0 && toY >= 0) {
6631                 premoveToX = toX;
6632                 premoveToY = toY;
6633                 premoveFromX = fromX;
6634                 premoveFromY = fromY;
6635                 premovePromoChar = promoChar;
6636                 gotPremove = 1;
6637                 if (appData.debugMode)
6638                     fprintf(debugFP, "Got premove: fromX %d,"
6639                             "fromY %d, toX %d, toY %d\n",
6640                             fromX, fromY, toX, toY);
6641             }
6642             return;
6643         }
6644         break;
6645
6646       default:
6647         break;
6648
6649       case EditPosition:
6650         /* EditPosition, empty square, or different color piece;
6651            click-click move is possible */
6652         if (toX == -2 || toY == -2) {
6653             boards[0][fromY][fromX] = EmptySquare;
6654             DrawPosition(FALSE, boards[currentMove]);
6655             return;
6656         } else if (toX >= 0 && toY >= 0) {
6657             boards[0][toY][toX] = boards[0][fromY][fromX];
6658             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6659                 if(boards[0][fromY][0] != EmptySquare) {
6660                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6661                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6662                 }
6663             } else
6664             if(fromX == BOARD_RGHT+1) {
6665                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6666                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6667                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6668                 }
6669             } else
6670             boards[0][fromY][fromX] = gatingPiece;
6671             DrawPosition(FALSE, boards[currentMove]);
6672             return;
6673         }
6674         return;
6675     }
6676
6677     if(toX < 0 || toY < 0) return;
6678     pdown = boards[currentMove][fromY][fromX];
6679     pup = boards[currentMove][toY][toX];
6680
6681     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6682     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6683          if( pup != EmptySquare ) return;
6684          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6685            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6686                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6687            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6688            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6689            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6690            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6691          fromY = DROP_RANK;
6692     }
6693
6694     /* [HGM] always test for legality, to get promotion info */
6695     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6696                                          fromY, fromX, toY, toX, promoChar);
6697
6698     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6699
6700     /* [HGM] but possibly ignore an IllegalMove result */
6701     if (appData.testLegality) {
6702         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6703             DisplayMoveError(_("Illegal move"));
6704             return;
6705         }
6706     }
6707
6708     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6709         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6710              ClearPremoveHighlights(); // was included
6711         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6712         return;
6713     }
6714
6715     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6716 }
6717
6718 /* Common tail of UserMoveEvent and DropMenuEvent */
6719 int
6720 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6721 {
6722     char *bookHit = 0;
6723
6724     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6725         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6726         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6727         if(WhiteOnMove(currentMove)) {
6728             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6729         } else {
6730             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6731         }
6732     }
6733
6734     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6735        move type in caller when we know the move is a legal promotion */
6736     if(moveType == NormalMove && promoChar)
6737         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6738
6739     /* [HGM] <popupFix> The following if has been moved here from
6740        UserMoveEvent(). Because it seemed to belong here (why not allow
6741        piece drops in training games?), and because it can only be
6742        performed after it is known to what we promote. */
6743     if (gameMode == Training) {
6744       /* compare the move played on the board to the next move in the
6745        * game. If they match, display the move and the opponent's response.
6746        * If they don't match, display an error message.
6747        */
6748       int saveAnimate;
6749       Board testBoard;
6750       CopyBoard(testBoard, boards[currentMove]);
6751       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6752
6753       if (CompareBoards(testBoard, boards[currentMove+1])) {
6754         ForwardInner(currentMove+1);
6755
6756         /* Autoplay the opponent's response.
6757          * if appData.animate was TRUE when Training mode was entered,
6758          * the response will be animated.
6759          */
6760         saveAnimate = appData.animate;
6761         appData.animate = animateTraining;
6762         ForwardInner(currentMove+1);
6763         appData.animate = saveAnimate;
6764
6765         /* check for the end of the game */
6766         if (currentMove >= forwardMostMove) {
6767           gameMode = PlayFromGameFile;
6768           ModeHighlight();
6769           SetTrainingModeOff();
6770           DisplayInformation(_("End of game"));
6771         }
6772       } else {
6773         DisplayError(_("Incorrect move"), 0);
6774       }
6775       return 1;
6776     }
6777
6778   /* Ok, now we know that the move is good, so we can kill
6779      the previous line in Analysis Mode */
6780   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6781                                 && currentMove < forwardMostMove) {
6782     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6783     else forwardMostMove = currentMove;
6784   }
6785
6786   ClearMap();
6787
6788   /* If we need the chess program but it's dead, restart it */
6789   ResurrectChessProgram();
6790
6791   /* A user move restarts a paused game*/
6792   if (pausing)
6793     PauseEvent();
6794
6795   thinkOutput[0] = NULLCHAR;
6796
6797   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6798
6799   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6800     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6801     return 1;
6802   }
6803
6804   if (gameMode == BeginningOfGame) {
6805     if (appData.noChessProgram) {
6806       gameMode = EditGame;
6807       SetGameInfo();
6808     } else {
6809       char buf[MSG_SIZ];
6810       gameMode = MachinePlaysBlack;
6811       StartClocks();
6812       SetGameInfo();
6813       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6814       DisplayTitle(buf);
6815       if (first.sendName) {
6816         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6817         SendToProgram(buf, &first);
6818       }
6819       StartClocks();
6820     }
6821     ModeHighlight();
6822   }
6823
6824   /* Relay move to ICS or chess engine */
6825   if (appData.icsActive) {
6826     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6827         gameMode == IcsExamining) {
6828       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6829         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6830         SendToICS("draw ");
6831         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6832       }
6833       // also send plain move, in case ICS does not understand atomic claims
6834       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6835       ics_user_moved = 1;
6836     }
6837   } else {
6838     if (first.sendTime && (gameMode == BeginningOfGame ||
6839                            gameMode == MachinePlaysWhite ||
6840                            gameMode == MachinePlaysBlack)) {
6841       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6842     }
6843     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6844          // [HGM] book: if program might be playing, let it use book
6845         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6846         first.maybeThinking = TRUE;
6847     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6848         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6849         SendBoard(&first, currentMove+1);
6850     } else SendMoveToProgram(forwardMostMove-1, &first);
6851     if (currentMove == cmailOldMove + 1) {
6852       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6853     }
6854   }
6855
6856   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6857
6858   switch (gameMode) {
6859   case EditGame:
6860     if(appData.testLegality)
6861     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6862     case MT_NONE:
6863     case MT_CHECK:
6864       break;
6865     case MT_CHECKMATE:
6866     case MT_STAINMATE:
6867       if (WhiteOnMove(currentMove)) {
6868         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6869       } else {
6870         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6871       }
6872       break;
6873     case MT_STALEMATE:
6874       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6875       break;
6876     }
6877     break;
6878
6879   case MachinePlaysBlack:
6880   case MachinePlaysWhite:
6881     /* disable certain menu options while machine is thinking */
6882     SetMachineThinkingEnables();
6883     break;
6884
6885   default:
6886     break;
6887   }
6888
6889   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6890   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6891
6892   if(bookHit) { // [HGM] book: simulate book reply
6893         static char bookMove[MSG_SIZ]; // a bit generous?
6894
6895         programStats.nodes = programStats.depth = programStats.time =
6896         programStats.score = programStats.got_only_move = 0;
6897         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6898
6899         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6900         strcat(bookMove, bookHit);
6901         HandleMachineMove(bookMove, &first);
6902   }
6903   return 1;
6904 }
6905
6906 void
6907 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6908 {
6909     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6910     Markers *m = (Markers *) closure;
6911     if(rf == fromY && ff == fromX)
6912         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6913                          || kind == WhiteCapturesEnPassant
6914                          || kind == BlackCapturesEnPassant);
6915     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6916 }
6917
6918 void
6919 MarkTargetSquares (int clear)
6920 {
6921   int x, y;
6922   if(clear) // no reason to ever suppress clearing
6923     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6924   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6925      !appData.testLegality || gameMode == EditPosition) return;
6926   if(!clear) {
6927     int capt = 0;
6928     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6929     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6930       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6931       if(capt)
6932       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6933     }
6934   }
6935   DrawPosition(FALSE, NULL);
6936 }
6937
6938 int
6939 Explode (Board board, int fromX, int fromY, int toX, int toY)
6940 {
6941     if(gameInfo.variant == VariantAtomic &&
6942        (board[toY][toX] != EmptySquare ||                     // capture?
6943         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6944                          board[fromY][fromX] == BlackPawn   )
6945       )) {
6946         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6947         return TRUE;
6948     }
6949     return FALSE;
6950 }
6951
6952 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6953
6954 int
6955 CanPromote (ChessSquare piece, int y)
6956 {
6957         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6958         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6959         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6960            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6961            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6962                                                   gameInfo.variant == VariantMakruk) return FALSE;
6963         return (piece == BlackPawn && y == 1 ||
6964                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6965                 piece == BlackLance && y == 1 ||
6966                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6967 }
6968
6969 void
6970 LeftClick (ClickType clickType, int xPix, int yPix)
6971 {
6972     int x, y;
6973     Boolean saveAnimate;
6974     static int second = 0, promotionChoice = 0, clearFlag = 0;
6975     char promoChoice = NULLCHAR;
6976     ChessSquare piece;
6977     static TimeMark lastClickTime, prevClickTime;
6978
6979     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
6980
6981     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
6982
6983     if (clickType == Press) ErrorPopDown();
6984
6985     x = EventToSquare(xPix, BOARD_WIDTH);
6986     y = EventToSquare(yPix, BOARD_HEIGHT);
6987     if (!flipView && y >= 0) {
6988         y = BOARD_HEIGHT - 1 - y;
6989     }
6990     if (flipView && x >= 0) {
6991         x = BOARD_WIDTH - 1 - x;
6992     }
6993
6994     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6995         defaultPromoChoice = promoSweep;
6996         promoSweep = EmptySquare;   // terminate sweep
6997         promoDefaultAltered = TRUE;
6998         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6999     }
7000
7001     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7002         if(clickType == Release) return; // ignore upclick of click-click destination
7003         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7004         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7005         if(gameInfo.holdingsWidth &&
7006                 (WhiteOnMove(currentMove)
7007                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7008                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7009             // click in right holdings, for determining promotion piece
7010             ChessSquare p = boards[currentMove][y][x];
7011             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7012             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7013             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7014                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7015                 fromX = fromY = -1;
7016                 return;
7017             }
7018         }
7019         DrawPosition(FALSE, boards[currentMove]);
7020         return;
7021     }
7022
7023     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7024     if(clickType == Press
7025             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7026               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7027               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7028         return;
7029
7030     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7031         // could be static click on premove from-square: abort premove
7032         gotPremove = 0;
7033         ClearPremoveHighlights();
7034     }
7035
7036     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7037         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7038
7039     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7040         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7041                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7042         defaultPromoChoice = DefaultPromoChoice(side);
7043     }
7044
7045     autoQueen = appData.alwaysPromoteToQueen;
7046
7047     if (fromX == -1) {
7048       int originalY = y;
7049       gatingPiece = EmptySquare;
7050       if (clickType != Press) {
7051         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7052             DragPieceEnd(xPix, yPix); dragging = 0;
7053             DrawPosition(FALSE, NULL);
7054         }
7055         return;
7056       }
7057       doubleClick = FALSE;
7058       fromX = x; fromY = y; toX = toY = -1;
7059       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7060          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7061          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7062             /* First square */
7063             if (OKToStartUserMove(fromX, fromY)) {
7064                 second = 0;
7065                 MarkTargetSquares(0);
7066                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7067                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7068                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7069                     promoSweep = defaultPromoChoice;
7070                     selectFlag = 0; lastX = xPix; lastY = yPix;
7071                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7072                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7073                 }
7074                 if (appData.highlightDragging) {
7075                     SetHighlights(fromX, fromY, -1, -1);
7076                 } else {
7077                     ClearHighlights();
7078                 }
7079             } else fromX = fromY = -1;
7080             return;
7081         }
7082     }
7083
7084     /* fromX != -1 */
7085     if (clickType == Press && gameMode != EditPosition) {
7086         ChessSquare fromP;
7087         ChessSquare toP;
7088         int frc;
7089
7090         // ignore off-board to clicks
7091         if(y < 0 || x < 0) return;
7092
7093         /* Check if clicking again on the same color piece */
7094         fromP = boards[currentMove][fromY][fromX];
7095         toP = boards[currentMove][y][x];
7096         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7097         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7098              WhitePawn <= toP && toP <= WhiteKing &&
7099              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7100              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7101             (BlackPawn <= fromP && fromP <= BlackKing &&
7102              BlackPawn <= toP && toP <= BlackKing &&
7103              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7104              !(fromP == BlackKing && toP == BlackRook && frc))) {
7105             /* Clicked again on same color piece -- changed his mind */
7106             second = (x == fromX && y == fromY);
7107             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7108                 second = FALSE; // first double-click rather than scond click
7109                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7110             }
7111             promoDefaultAltered = FALSE;
7112             MarkTargetSquares(1);
7113            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7114             if (appData.highlightDragging) {
7115                 SetHighlights(x, y, -1, -1);
7116             } else {
7117                 ClearHighlights();
7118             }
7119             if (OKToStartUserMove(x, y)) {
7120                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7121                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7122                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7123                  gatingPiece = boards[currentMove][fromY][fromX];
7124                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7125                 fromX = x;
7126                 fromY = y; dragging = 1;
7127                 MarkTargetSquares(0);
7128                 DragPieceBegin(xPix, yPix, FALSE);
7129                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7130                     promoSweep = defaultPromoChoice;
7131                     selectFlag = 0; lastX = xPix; lastY = yPix;
7132                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7133                 }
7134             }
7135            }
7136            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7137            second = FALSE; 
7138         }
7139         // ignore clicks on holdings
7140         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7141     }
7142
7143     if (clickType == Release && x == fromX && y == fromY) {
7144         DragPieceEnd(xPix, yPix); dragging = 0;
7145         if(clearFlag) {
7146             // a deferred attempt to click-click move an empty square on top of a piece
7147             boards[currentMove][y][x] = EmptySquare;
7148             ClearHighlights();
7149             DrawPosition(FALSE, boards[currentMove]);
7150             fromX = fromY = -1; clearFlag = 0;
7151             return;
7152         }
7153         if (appData.animateDragging) {
7154             /* Undo animation damage if any */
7155             DrawPosition(FALSE, NULL);
7156         }
7157         if (second) {
7158             /* Second up/down in same square; just abort move */
7159             second = 0;
7160             fromX = fromY = -1;
7161             gatingPiece = EmptySquare;
7162             ClearHighlights();
7163             gotPremove = 0;
7164             ClearPremoveHighlights();
7165         } else {
7166             /* First upclick in same square; start click-click mode */
7167             SetHighlights(x, y, -1, -1);
7168         }
7169         return;
7170     }
7171
7172     clearFlag = 0;
7173
7174     /* we now have a different from- and (possibly off-board) to-square */
7175     /* Completed move */
7176     toX = x;
7177     toY = y;
7178     saveAnimate = appData.animate;
7179     if (clickType == Press) {
7180         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7181             // must be Edit Position mode with empty-square selected
7182             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7183             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7184             return;
7185         }
7186         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7187           if(appData.sweepSelect) {
7188             ChessSquare piece = boards[currentMove][fromY][fromX];
7189             ChessSquare victim = boards[currentMove][toY][toX];
7190             boards[currentMove][toY][toX] = piece; // kludge: make sure there is something to grab for drag
7191             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7192             boards[currentMove][toY][toX] = victim;
7193             promoSweep = defaultPromoChoice;
7194             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7195             selectFlag = 0; lastX = xPix; lastY = yPix;
7196             Sweep(0); // Pawn that is going to promote: preview promotion piece
7197             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7198             DrawPosition(FALSE, boards[currentMove]);
7199           }
7200           return; // promo popup appears on up-click
7201         }
7202         /* Finish clickclick move */
7203         if (appData.animate || appData.highlightLastMove) {
7204             SetHighlights(fromX, fromY, toX, toY);
7205         } else {
7206             ClearHighlights();
7207         }
7208     } else {
7209         /* Finish drag move */
7210         if (appData.highlightLastMove) {
7211             SetHighlights(fromX, fromY, toX, toY);
7212         } else {
7213             ClearHighlights();
7214         }
7215         DragPieceEnd(xPix, yPix); dragging = 0;
7216         /* Don't animate move and drag both */
7217         appData.animate = FALSE;
7218     }
7219     MarkTargetSquares(1);
7220
7221     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7222     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7223         ChessSquare piece = boards[currentMove][fromY][fromX];
7224         if(gameMode == EditPosition && piece != EmptySquare &&
7225            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7226             int n;
7227
7228             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7229                 n = PieceToNumber(piece - (int)BlackPawn);
7230                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7231                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7232                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7233             } else
7234             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7235                 n = PieceToNumber(piece);
7236                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7237                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7238                 boards[currentMove][n][BOARD_WIDTH-2]++;
7239             }
7240             boards[currentMove][fromY][fromX] = EmptySquare;
7241         }
7242         ClearHighlights();
7243         fromX = fromY = -1;
7244         DrawPosition(TRUE, boards[currentMove]);
7245         return;
7246     }
7247
7248     // off-board moves should not be highlighted
7249     if(x < 0 || y < 0) ClearHighlights();
7250
7251     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7252
7253     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7254         SetHighlights(fromX, fromY, toX, toY);
7255         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7256             // [HGM] super: promotion to captured piece selected from holdings
7257             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7258             promotionChoice = TRUE;
7259             // kludge follows to temporarily execute move on display, without promoting yet
7260             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7261             boards[currentMove][toY][toX] = p;
7262             DrawPosition(FALSE, boards[currentMove]);
7263             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7264             boards[currentMove][toY][toX] = q;
7265             DisplayMessage("Click in holdings to choose piece", "");
7266             return;
7267         }
7268         PromotionPopUp();
7269     } else {
7270         int oldMove = currentMove;
7271         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7272         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7273         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7274         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7275            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7276             DrawPosition(TRUE, boards[currentMove]);
7277         fromX = fromY = -1;
7278     }
7279     appData.animate = saveAnimate;
7280     if (appData.animate || appData.animateDragging) {
7281         /* Undo animation damage if needed */
7282         DrawPosition(FALSE, NULL);
7283     }
7284 }
7285
7286 int
7287 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7288 {   // front-end-free part taken out of PieceMenuPopup
7289     int whichMenu; int xSqr, ySqr;
7290
7291     if(seekGraphUp) { // [HGM] seekgraph
7292         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7293         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7294         return -2;
7295     }
7296
7297     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7298          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7299         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7300         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7301         if(action == Press)   {
7302             originalFlip = flipView;
7303             flipView = !flipView; // temporarily flip board to see game from partners perspective
7304             DrawPosition(TRUE, partnerBoard);
7305             DisplayMessage(partnerStatus, "");
7306             partnerUp = TRUE;
7307         } else if(action == Release) {
7308             flipView = originalFlip;
7309             DrawPosition(TRUE, boards[currentMove]);
7310             partnerUp = FALSE;
7311         }
7312         return -2;
7313     }
7314
7315     xSqr = EventToSquare(x, BOARD_WIDTH);
7316     ySqr = EventToSquare(y, BOARD_HEIGHT);
7317     if (action == Release) {
7318         if(pieceSweep != EmptySquare) {
7319             EditPositionMenuEvent(pieceSweep, toX, toY);
7320             pieceSweep = EmptySquare;
7321         } else UnLoadPV(); // [HGM] pv
7322     }
7323     if (action != Press) return -2; // return code to be ignored
7324     switch (gameMode) {
7325       case IcsExamining:
7326         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7327       case EditPosition:
7328         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7329         if (xSqr < 0 || ySqr < 0) return -1;
7330         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7331         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7332         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7333         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7334         NextPiece(0);
7335         return 2; // grab
7336       case IcsObserving:
7337         if(!appData.icsEngineAnalyze) return -1;
7338       case IcsPlayingWhite:
7339       case IcsPlayingBlack:
7340         if(!appData.zippyPlay) goto noZip;
7341       case AnalyzeMode:
7342       case AnalyzeFile:
7343       case MachinePlaysWhite:
7344       case MachinePlaysBlack:
7345       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7346         if (!appData.dropMenu) {
7347           LoadPV(x, y);
7348           return 2; // flag front-end to grab mouse events
7349         }
7350         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7351            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7352       case EditGame:
7353       noZip:
7354         if (xSqr < 0 || ySqr < 0) return -1;
7355         if (!appData.dropMenu || appData.testLegality &&
7356             gameInfo.variant != VariantBughouse &&
7357             gameInfo.variant != VariantCrazyhouse) return -1;
7358         whichMenu = 1; // drop menu
7359         break;
7360       default:
7361         return -1;
7362     }
7363
7364     if (((*fromX = xSqr) < 0) ||
7365         ((*fromY = ySqr) < 0)) {
7366         *fromX = *fromY = -1;
7367         return -1;
7368     }
7369     if (flipView)
7370       *fromX = BOARD_WIDTH - 1 - *fromX;
7371     else
7372       *fromY = BOARD_HEIGHT - 1 - *fromY;
7373
7374     return whichMenu;
7375 }
7376
7377 void
7378 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7379 {
7380 //    char * hint = lastHint;
7381     FrontEndProgramStats stats;
7382
7383     stats.which = cps == &first ? 0 : 1;
7384     stats.depth = cpstats->depth;
7385     stats.nodes = cpstats->nodes;
7386     stats.score = cpstats->score;
7387     stats.time = cpstats->time;
7388     stats.pv = cpstats->movelist;
7389     stats.hint = lastHint;
7390     stats.an_move_index = 0;
7391     stats.an_move_count = 0;
7392
7393     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7394         stats.hint = cpstats->move_name;
7395         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7396         stats.an_move_count = cpstats->nr_moves;
7397     }
7398
7399     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
7400
7401     SetProgramStats( &stats );
7402 }
7403
7404 void
7405 ClearEngineOutputPane (int which)
7406 {
7407     static FrontEndProgramStats dummyStats;
7408     dummyStats.which = which;
7409     dummyStats.pv = "#";
7410     SetProgramStats( &dummyStats );
7411 }
7412
7413 #define MAXPLAYERS 500
7414
7415 char *
7416 TourneyStandings (int display)
7417 {
7418     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7419     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7420     char result, *p, *names[MAXPLAYERS];
7421
7422     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7423         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7424     names[0] = p = strdup(appData.participants);
7425     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7426
7427     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7428
7429     while(result = appData.results[nr]) {
7430         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7431         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7432         wScore = bScore = 0;
7433         switch(result) {
7434           case '+': wScore = 2; break;
7435           case '-': bScore = 2; break;
7436           case '=': wScore = bScore = 1; break;
7437           case ' ':
7438           case '*': return strdup("busy"); // tourney not finished
7439         }
7440         score[w] += wScore;
7441         score[b] += bScore;
7442         games[w]++;
7443         games[b]++;
7444         nr++;
7445     }
7446     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7447     for(w=0; w<nPlayers; w++) {
7448         bScore = -1;
7449         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7450         ranking[w] = b; points[w] = bScore; score[b] = -2;
7451     }
7452     p = malloc(nPlayers*34+1);
7453     for(w=0; w<nPlayers && w<display; w++)
7454         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7455     free(names[0]);
7456     return p;
7457 }
7458
7459 void
7460 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7461 {       // count all piece types
7462         int p, f, r;
7463         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7464         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7465         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7466                 p = board[r][f];
7467                 pCnt[p]++;
7468                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7469                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7470                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7471                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7472                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7473                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7474         }
7475 }
7476
7477 int
7478 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7479 {
7480         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7481         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7482
7483         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7484         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7485         if(myPawns == 2 && nMine == 3) // KPP
7486             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7487         if(myPawns == 1 && nMine == 2) // KP
7488             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7489         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7490             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7491         if(myPawns) return FALSE;
7492         if(pCnt[WhiteRook+side])
7493             return pCnt[BlackRook-side] ||
7494                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7495                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7496                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7497         if(pCnt[WhiteCannon+side]) {
7498             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7499             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7500         }
7501         if(pCnt[WhiteKnight+side])
7502             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7503         return FALSE;
7504 }
7505
7506 int
7507 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7508 {
7509         VariantClass v = gameInfo.variant;
7510
7511         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7512         if(v == VariantShatranj) return TRUE; // always winnable through baring
7513         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7514         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7515
7516         if(v == VariantXiangqi) {
7517                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7518
7519                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7520                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7521                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7522                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7523                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7524                 if(stale) // we have at least one last-rank P plus perhaps C
7525                     return majors // KPKX
7526                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7527                 else // KCA*E*
7528                     return pCnt[WhiteFerz+side] // KCAK
7529                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7530                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7531                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7532
7533         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7534                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7535
7536                 if(nMine == 1) return FALSE; // bare King
7537                 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
7538                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7539                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7540                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7541                 if(pCnt[WhiteKnight+side])
7542                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7543                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7544                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7545                 if(nBishops)
7546                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7547                 if(pCnt[WhiteAlfil+side])
7548                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7549                 if(pCnt[WhiteWazir+side])
7550                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7551         }
7552
7553         return TRUE;
7554 }
7555
7556 int
7557 CompareWithRights (Board b1, Board b2)
7558 {
7559     int rights = 0;
7560     if(!CompareBoards(b1, b2)) return FALSE;
7561     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7562     /* compare castling rights */
7563     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7564            rights++; /* King lost rights, while rook still had them */
7565     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7566         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7567            rights++; /* but at least one rook lost them */
7568     }
7569     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7570            rights++;
7571     if( b1[CASTLING][5] != NoRights ) {
7572         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7573            rights++;
7574     }
7575     return rights == 0;
7576 }
7577
7578 int
7579 Adjudicate (ChessProgramState *cps)
7580 {       // [HGM] some adjudications useful with buggy engines
7581         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7582         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7583         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7584         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7585         int k, count = 0; static int bare = 1;
7586         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7587         Boolean canAdjudicate = !appData.icsActive;
7588
7589         // most tests only when we understand the game, i.e. legality-checking on
7590             if( appData.testLegality )
7591             {   /* [HGM] Some more adjudications for obstinate engines */
7592                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7593                 static int moveCount = 6;
7594                 ChessMove result;
7595                 char *reason = NULL;
7596
7597                 /* Count what is on board. */
7598                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7599
7600                 /* Some material-based adjudications that have to be made before stalemate test */
7601                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7602                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7603                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7604                      if(canAdjudicate && appData.checkMates) {
7605                          if(engineOpponent)
7606                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7607                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7608                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7609                          return 1;
7610                      }
7611                 }
7612
7613                 /* Bare King in Shatranj (loses) or Losers (wins) */
7614                 if( nrW == 1 || nrB == 1) {
7615                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7616                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7617                      if(canAdjudicate && appData.checkMates) {
7618                          if(engineOpponent)
7619                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7620                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7621                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7622                          return 1;
7623                      }
7624                   } else
7625                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7626                   {    /* bare King */
7627                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7628                         if(canAdjudicate && appData.checkMates) {
7629                             /* but only adjudicate if adjudication enabled */
7630                             if(engineOpponent)
7631                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7632                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7633                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7634                             return 1;
7635                         }
7636                   }
7637                 } else bare = 1;
7638
7639
7640             // don't wait for engine to announce game end if we can judge ourselves
7641             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7642               case MT_CHECK:
7643                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7644                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7645                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7646                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7647                             checkCnt++;
7648                         if(checkCnt >= 2) {
7649                             reason = "Xboard adjudication: 3rd check";
7650                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7651                             break;
7652                         }
7653                     }
7654                 }
7655               case MT_NONE:
7656               default:
7657                 break;
7658               case MT_STALEMATE:
7659               case MT_STAINMATE:
7660                 reason = "Xboard adjudication: Stalemate";
7661                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7662                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7663                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7664                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7665                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7666                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7667                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7668                                                                         EP_CHECKMATE : EP_WINS);
7669                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7670                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7671                 }
7672                 break;
7673               case MT_CHECKMATE:
7674                 reason = "Xboard adjudication: Checkmate";
7675                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7676                 break;
7677             }
7678
7679                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7680                     case EP_STALEMATE:
7681                         result = GameIsDrawn; break;
7682                     case EP_CHECKMATE:
7683                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7684                     case EP_WINS:
7685                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7686                     default:
7687                         result = EndOfFile;
7688                 }
7689                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7690                     if(engineOpponent)
7691                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7692                     GameEnds( result, reason, GE_XBOARD );
7693                     return 1;
7694                 }
7695
7696                 /* Next absolutely insufficient mating material. */
7697                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7698                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7699                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7700
7701                      /* always flag draws, for judging claims */
7702                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7703
7704                      if(canAdjudicate && appData.materialDraws) {
7705                          /* but only adjudicate them if adjudication enabled */
7706                          if(engineOpponent) {
7707                            SendToProgram("force\n", engineOpponent); // suppress reply
7708                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7709                          }
7710                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7711                          return 1;
7712                      }
7713                 }
7714
7715                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7716                 if(gameInfo.variant == VariantXiangqi ?
7717                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7718                  : nrW + nrB == 4 &&
7719                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7720                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7721                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7722                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7723                    ) ) {
7724                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7725                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7726                           if(engineOpponent) {
7727                             SendToProgram("force\n", engineOpponent); // suppress reply
7728                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7729                           }
7730                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7731                           return 1;
7732                      }
7733                 } else moveCount = 6;
7734             }
7735
7736         // Repetition draws and 50-move rule can be applied independently of legality testing
7737
7738                 /* Check for rep-draws */
7739                 count = 0;
7740                 for(k = forwardMostMove-2;
7741                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7742                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7743                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7744                     k-=2)
7745                 {   int rights=0;
7746                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7747                         /* compare castling rights */
7748                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7749                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7750                                 rights++; /* King lost rights, while rook still had them */
7751                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7752                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7753                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7754                                    rights++; /* but at least one rook lost them */
7755                         }
7756                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7757                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7758                                 rights++;
7759                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7760                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7761                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7762                                    rights++;
7763                         }
7764                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7765                             && appData.drawRepeats > 1) {
7766                              /* adjudicate after user-specified nr of repeats */
7767                              int result = GameIsDrawn;
7768                              char *details = "XBoard adjudication: repetition draw";
7769                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7770                                 // [HGM] xiangqi: check for forbidden perpetuals
7771                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7772                                 for(m=forwardMostMove; m>k; m-=2) {
7773                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7774                                         ourPerpetual = 0; // the current mover did not always check
7775                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7776                                         hisPerpetual = 0; // the opponent did not always check
7777                                 }
7778                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7779                                                                         ourPerpetual, hisPerpetual);
7780                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7781                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7782                                     details = "Xboard adjudication: perpetual checking";
7783                                 } else
7784                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7785                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7786                                 } else
7787                                 // Now check for perpetual chases
7788                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7789                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7790                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7791                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7792                                         static char resdet[MSG_SIZ];
7793                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7794                                         details = resdet;
7795                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7796                                     } else
7797                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7798                                         break; // Abort repetition-checking loop.
7799                                 }
7800                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7801                              }
7802                              if(engineOpponent) {
7803                                SendToProgram("force\n", engineOpponent); // suppress reply
7804                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7805                              }
7806                              GameEnds( result, details, GE_XBOARD );
7807                              return 1;
7808                         }
7809                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7810                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7811                     }
7812                 }
7813
7814                 /* Now we test for 50-move draws. Determine ply count */
7815                 count = forwardMostMove;
7816                 /* look for last irreversble move */
7817                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7818                     count--;
7819                 /* if we hit starting position, add initial plies */
7820                 if( count == backwardMostMove )
7821                     count -= initialRulePlies;
7822                 count = forwardMostMove - count;
7823                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7824                         // adjust reversible move counter for checks in Xiangqi
7825                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7826                         if(i < backwardMostMove) i = backwardMostMove;
7827                         while(i <= forwardMostMove) {
7828                                 lastCheck = inCheck; // check evasion does not count
7829                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7830                                 if(inCheck || lastCheck) count--; // check does not count
7831                                 i++;
7832                         }
7833                 }
7834                 if( count >= 100)
7835                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7836                          /* this is used to judge if draw claims are legal */
7837                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7838                          if(engineOpponent) {
7839                            SendToProgram("force\n", engineOpponent); // suppress reply
7840                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7841                          }
7842                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7843                          return 1;
7844                 }
7845
7846                 /* if draw offer is pending, treat it as a draw claim
7847                  * when draw condition present, to allow engines a way to
7848                  * claim draws before making their move to avoid a race
7849                  * condition occurring after their move
7850                  */
7851                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7852                          char *p = NULL;
7853                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7854                              p = "Draw claim: 50-move rule";
7855                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7856                              p = "Draw claim: 3-fold repetition";
7857                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7858                              p = "Draw claim: insufficient mating material";
7859                          if( p != NULL && canAdjudicate) {
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, p, GE_XBOARD );
7865                              return 1;
7866                          }
7867                 }
7868
7869                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7870                     if(engineOpponent) {
7871                       SendToProgram("force\n", engineOpponent); // suppress reply
7872                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7873                     }
7874                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7875                     return 1;
7876                 }
7877         return 0;
7878 }
7879
7880 char *
7881 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7882 {   // [HGM] book: this routine intercepts moves to simulate book replies
7883     char *bookHit = NULL;
7884
7885     //first determine if the incoming move brings opponent into his book
7886     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7887         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7888     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7889     if(bookHit != NULL && !cps->bookSuspend) {
7890         // make sure opponent is not going to reply after receiving move to book position
7891         SendToProgram("force\n", cps);
7892         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7893     }
7894     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7895     // now arrange restart after book miss
7896     if(bookHit) {
7897         // after a book hit we never send 'go', and the code after the call to this routine
7898         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7899         char buf[MSG_SIZ], *move = bookHit;
7900         if(cps->useSAN) {
7901             int fromX, fromY, toX, toY;
7902             char promoChar;
7903             ChessMove moveType;
7904             move = buf + 30;
7905             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7906                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7907                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7908                                     PosFlags(forwardMostMove),
7909                                     fromY, fromX, toY, toX, promoChar, move);
7910             } else {
7911                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7912                 bookHit = NULL;
7913             }
7914         }
7915         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7916         SendToProgram(buf, cps);
7917         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7918     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7919         SendToProgram("go\n", cps);
7920         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7921     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7922         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7923             SendToProgram("go\n", cps);
7924         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7925     }
7926     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7927 }
7928
7929 int
7930 LoadError (char *errmess, ChessProgramState *cps)
7931 {   // unloads engine and switches back to -ncp mode if it was first
7932     if(cps->initDone) return FALSE;
7933     cps->isr = NULL; // this should suppress further error popups from breaking pipes
7934     DestroyChildProcess(cps->pr, 9 ); // just to be sure
7935     cps->pr = NoProc; 
7936     if(cps == &first) {
7937         appData.noChessProgram = TRUE;
7938         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
7939         gameMode = BeginningOfGame; ModeHighlight();
7940         SetNCPMode();
7941     }
7942     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
7943     DisplayMessage("", ""); // erase waiting message
7944     if(errmess) DisplayError(errmess, 0); // announce reason, if given
7945     return TRUE;
7946 }
7947
7948 char *savedMessage;
7949 ChessProgramState *savedState;
7950 void
7951 DeferredBookMove (void)
7952 {
7953         if(savedState->lastPing != savedState->lastPong)
7954                     ScheduleDelayedEvent(DeferredBookMove, 10);
7955         else
7956         HandleMachineMove(savedMessage, savedState);
7957 }
7958
7959 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7960
7961 void
7962 HandleMachineMove (char *message, ChessProgramState *cps)
7963 {
7964     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7965     char realname[MSG_SIZ];
7966     int fromX, fromY, toX, toY;
7967     ChessMove moveType;
7968     char promoChar;
7969     char *p, *pv=buf1;
7970     int machineWhite, oldError;
7971     char *bookHit;
7972
7973     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7974         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7975         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7976             DisplayError(_("Invalid pairing from pairing engine"), 0);
7977             return;
7978         }
7979         pairingReceived = 1;
7980         NextMatchGame();
7981         return; // Skim the pairing messages here.
7982     }
7983
7984     oldError = cps->userError; cps->userError = 0;
7985
7986 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7987     /*
7988      * Kludge to ignore BEL characters
7989      */
7990     while (*message == '\007') message++;
7991
7992     /*
7993      * [HGM] engine debug message: ignore lines starting with '#' character
7994      */
7995     if(cps->debug && *message == '#') return;
7996
7997     /*
7998      * Look for book output
7999      */
8000     if (cps == &first && bookRequested) {
8001         if (message[0] == '\t' || message[0] == ' ') {
8002             /* Part of the book output is here; append it */
8003             strcat(bookOutput, message);
8004             strcat(bookOutput, "  \n");
8005             return;
8006         } else if (bookOutput[0] != NULLCHAR) {
8007             /* All of book output has arrived; display it */
8008             char *p = bookOutput;
8009             while (*p != NULLCHAR) {
8010                 if (*p == '\t') *p = ' ';
8011                 p++;
8012             }
8013             DisplayInformation(bookOutput);
8014             bookRequested = FALSE;
8015             /* Fall through to parse the current output */
8016         }
8017     }
8018
8019     /*
8020      * Look for machine move.
8021      */
8022     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8023         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8024     {
8025         /* This method is only useful on engines that support ping */
8026         if (cps->lastPing != cps->lastPong) {
8027           if (gameMode == BeginningOfGame) {
8028             /* Extra move from before last new; ignore */
8029             if (appData.debugMode) {
8030                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8031             }
8032           } else {
8033             if (appData.debugMode) {
8034                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8035                         cps->which, gameMode);
8036             }
8037
8038             SendToProgram("undo\n", cps);
8039           }
8040           return;
8041         }
8042
8043         switch (gameMode) {
8044           case BeginningOfGame:
8045             /* Extra move from before last reset; ignore */
8046             if (appData.debugMode) {
8047                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8048             }
8049             return;
8050
8051           case EndOfGame:
8052           case IcsIdle:
8053           default:
8054             /* Extra move after we tried to stop.  The mode test is
8055                not a reliable way of detecting this problem, but it's
8056                the best we can do on engines that don't support ping.
8057             */
8058             if (appData.debugMode) {
8059                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8060                         cps->which, gameMode);
8061             }
8062             SendToProgram("undo\n", cps);
8063             return;
8064
8065           case MachinePlaysWhite:
8066           case IcsPlayingWhite:
8067             machineWhite = TRUE;
8068             break;
8069
8070           case MachinePlaysBlack:
8071           case IcsPlayingBlack:
8072             machineWhite = FALSE;
8073             break;
8074
8075           case TwoMachinesPlay:
8076             machineWhite = (cps->twoMachinesColor[0] == 'w');
8077             break;
8078         }
8079         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8080             if (appData.debugMode) {
8081                 fprintf(debugFP,
8082                         "Ignoring move out of turn by %s, gameMode %d"
8083                         ", forwardMost %d\n",
8084                         cps->which, gameMode, forwardMostMove);
8085             }
8086             return;
8087         }
8088
8089         if(cps->alphaRank) AlphaRank(machineMove, 4);
8090         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8091                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8092             /* Machine move could not be parsed; ignore it. */
8093           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8094                     machineMove, _(cps->which));
8095             DisplayError(buf1, 0);
8096             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8097                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8098             if (gameMode == TwoMachinesPlay) {
8099               GameEnds(machineWhite ? BlackWins : WhiteWins,
8100                        buf1, GE_XBOARD);
8101             }
8102             return;
8103         }
8104
8105         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8106         /* So we have to redo legality test with true e.p. status here,  */
8107         /* to make sure an illegal e.p. capture does not slip through,   */
8108         /* to cause a forfeit on a justified illegal-move complaint      */
8109         /* of the opponent.                                              */
8110         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8111            ChessMove moveType;
8112            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8113                              fromY, fromX, toY, toX, promoChar);
8114             if(moveType == IllegalMove) {
8115               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8116                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8117                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8118                            buf1, GE_XBOARD);
8119                 return;
8120            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8121            /* [HGM] Kludge to handle engines that send FRC-style castling
8122               when they shouldn't (like TSCP-Gothic) */
8123            switch(moveType) {
8124              case WhiteASideCastleFR:
8125              case BlackASideCastleFR:
8126                toX+=2;
8127                currentMoveString[2]++;
8128                break;
8129              case WhiteHSideCastleFR:
8130              case BlackHSideCastleFR:
8131                toX--;
8132                currentMoveString[2]--;
8133                break;
8134              default: ; // nothing to do, but suppresses warning of pedantic compilers
8135            }
8136         }
8137         hintRequested = FALSE;
8138         lastHint[0] = NULLCHAR;
8139         bookRequested = FALSE;
8140         /* Program may be pondering now */
8141         cps->maybeThinking = TRUE;
8142         if (cps->sendTime == 2) cps->sendTime = 1;
8143         if (cps->offeredDraw) cps->offeredDraw--;
8144
8145         /* [AS] Save move info*/
8146         pvInfoList[ forwardMostMove ].score = programStats.score;
8147         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8148         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8149
8150         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8151
8152         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8153         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8154             int count = 0;
8155
8156             while( count < adjudicateLossPlies ) {
8157                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8158
8159                 if( count & 1 ) {
8160                     score = -score; /* Flip score for winning side */
8161                 }
8162
8163                 if( score > adjudicateLossThreshold ) {
8164                     break;
8165                 }
8166
8167                 count++;
8168             }
8169
8170             if( count >= adjudicateLossPlies ) {
8171                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8172
8173                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8174                     "Xboard adjudication",
8175                     GE_XBOARD );
8176
8177                 return;
8178             }
8179         }
8180
8181         if(Adjudicate(cps)) {
8182             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8183             return; // [HGM] adjudicate: for all automatic game ends
8184         }
8185
8186 #if ZIPPY
8187         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8188             first.initDone) {
8189           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8190                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8191                 SendToICS("draw ");
8192                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8193           }
8194           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8195           ics_user_moved = 1;
8196           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8197                 char buf[3*MSG_SIZ];
8198
8199                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8200                         programStats.score / 100.,
8201                         programStats.depth,
8202                         programStats.time / 100.,
8203                         (unsigned int)programStats.nodes,
8204                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8205                         programStats.movelist);
8206                 SendToICS(buf);
8207 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8208           }
8209         }
8210 #endif
8211
8212         /* [AS] Clear stats for next move */
8213         ClearProgramStats();
8214         thinkOutput[0] = NULLCHAR;
8215         hiddenThinkOutputState = 0;
8216
8217         bookHit = NULL;
8218         if (gameMode == TwoMachinesPlay) {
8219             /* [HGM] relaying draw offers moved to after reception of move */
8220             /* and interpreting offer as claim if it brings draw condition */
8221             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8222                 SendToProgram("draw\n", cps->other);
8223             }
8224             if (cps->other->sendTime) {
8225                 SendTimeRemaining(cps->other,
8226                                   cps->other->twoMachinesColor[0] == 'w');
8227             }
8228             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8229             if (firstMove && !bookHit) {
8230                 firstMove = FALSE;
8231                 if (cps->other->useColors) {
8232                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8233                 }
8234                 SendToProgram("go\n", cps->other);
8235             }
8236             cps->other->maybeThinking = TRUE;
8237         }
8238
8239         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8240
8241         if (!pausing && appData.ringBellAfterMoves) {
8242             RingBell();
8243         }
8244
8245         /*
8246          * Reenable menu items that were disabled while
8247          * machine was thinking
8248          */
8249         if (gameMode != TwoMachinesPlay)
8250             SetUserThinkingEnables();
8251
8252         // [HGM] book: after book hit opponent has received move and is now in force mode
8253         // force the book reply into it, and then fake that it outputted this move by jumping
8254         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8255         if(bookHit) {
8256                 static char bookMove[MSG_SIZ]; // a bit generous?
8257
8258                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8259                 strcat(bookMove, bookHit);
8260                 message = bookMove;
8261                 cps = cps->other;
8262                 programStats.nodes = programStats.depth = programStats.time =
8263                 programStats.score = programStats.got_only_move = 0;
8264                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8265
8266                 if(cps->lastPing != cps->lastPong) {
8267                     savedMessage = message; // args for deferred call
8268                     savedState = cps;
8269                     ScheduleDelayedEvent(DeferredBookMove, 10);
8270                     return;
8271                 }
8272                 goto FakeBookMove;
8273         }
8274
8275         return;
8276     }
8277
8278     /* Set special modes for chess engines.  Later something general
8279      *  could be added here; for now there is just one kludge feature,
8280      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8281      *  when "xboard" is given as an interactive command.
8282      */
8283     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8284         cps->useSigint = FALSE;
8285         cps->useSigterm = FALSE;
8286     }
8287     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8288       ParseFeatures(message+8, cps);
8289       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8290     }
8291
8292     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8293                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8294       int dummy, s=6; char buf[MSG_SIZ];
8295       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8296       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8297       if(startedFromSetupPosition) return;
8298       ParseFEN(boards[0], &dummy, message+s);
8299       DrawPosition(TRUE, boards[0]);
8300       startedFromSetupPosition = TRUE;
8301       return;
8302     }
8303     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8304      * want this, I was asked to put it in, and obliged.
8305      */
8306     if (!strncmp(message, "setboard ", 9)) {
8307         Board initial_position;
8308
8309         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8310
8311         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8312             DisplayError(_("Bad FEN received from engine"), 0);
8313             return ;
8314         } else {
8315            Reset(TRUE, FALSE);
8316            CopyBoard(boards[0], initial_position);
8317            initialRulePlies = FENrulePlies;
8318            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8319            else gameMode = MachinePlaysBlack;
8320            DrawPosition(FALSE, boards[currentMove]);
8321         }
8322         return;
8323     }
8324
8325     /*
8326      * Look for communication commands
8327      */
8328     if (!strncmp(message, "telluser ", 9)) {
8329         if(message[9] == '\\' && message[10] == '\\')
8330             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8331         PlayTellSound();
8332         DisplayNote(message + 9);
8333         return;
8334     }
8335     if (!strncmp(message, "tellusererror ", 14)) {
8336         cps->userError = 1;
8337         if(message[14] == '\\' && message[15] == '\\')
8338             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8339         PlayTellSound();
8340         DisplayError(message + 14, 0);
8341         return;
8342     }
8343     if (!strncmp(message, "tellopponent ", 13)) {
8344       if (appData.icsActive) {
8345         if (loggedOn) {
8346           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8347           SendToICS(buf1);
8348         }
8349       } else {
8350         DisplayNote(message + 13);
8351       }
8352       return;
8353     }
8354     if (!strncmp(message, "tellothers ", 11)) {
8355       if (appData.icsActive) {
8356         if (loggedOn) {
8357           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8358           SendToICS(buf1);
8359         }
8360       }
8361       return;
8362     }
8363     if (!strncmp(message, "tellall ", 8)) {
8364       if (appData.icsActive) {
8365         if (loggedOn) {
8366           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8367           SendToICS(buf1);
8368         }
8369       } else {
8370         DisplayNote(message + 8);
8371       }
8372       return;
8373     }
8374     if (strncmp(message, "warning", 7) == 0) {
8375         /* Undocumented feature, use tellusererror in new code */
8376         DisplayError(message, 0);
8377         return;
8378     }
8379     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8380         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8381         strcat(realname, " query");
8382         AskQuestion(realname, buf2, buf1, cps->pr);
8383         return;
8384     }
8385     /* Commands from the engine directly to ICS.  We don't allow these to be
8386      *  sent until we are logged on. Crafty kibitzes have been known to
8387      *  interfere with the login process.
8388      */
8389     if (loggedOn) {
8390         if (!strncmp(message, "tellics ", 8)) {
8391             SendToICS(message + 8);
8392             SendToICS("\n");
8393             return;
8394         }
8395         if (!strncmp(message, "tellicsnoalias ", 15)) {
8396             SendToICS(ics_prefix);
8397             SendToICS(message + 15);
8398             SendToICS("\n");
8399             return;
8400         }
8401         /* The following are for backward compatibility only */
8402         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8403             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8404             SendToICS(ics_prefix);
8405             SendToICS(message);
8406             SendToICS("\n");
8407             return;
8408         }
8409     }
8410     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8411         return;
8412     }
8413     /*
8414      * If the move is illegal, cancel it and redraw the board.
8415      * Also deal with other error cases.  Matching is rather loose
8416      * here to accommodate engines written before the spec.
8417      */
8418     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8419         strncmp(message, "Error", 5) == 0) {
8420         if (StrStr(message, "name") ||
8421             StrStr(message, "rating") || StrStr(message, "?") ||
8422             StrStr(message, "result") || StrStr(message, "board") ||
8423             StrStr(message, "bk") || StrStr(message, "computer") ||
8424             StrStr(message, "variant") || StrStr(message, "hint") ||
8425             StrStr(message, "random") || StrStr(message, "depth") ||
8426             StrStr(message, "accepted")) {
8427             return;
8428         }
8429         if (StrStr(message, "protover")) {
8430           /* Program is responding to input, so it's apparently done
8431              initializing, and this error message indicates it is
8432              protocol version 1.  So we don't need to wait any longer
8433              for it to initialize and send feature commands. */
8434           FeatureDone(cps, 1);
8435           cps->protocolVersion = 1;
8436           return;
8437         }
8438         cps->maybeThinking = FALSE;
8439
8440         if (StrStr(message, "draw")) {
8441             /* Program doesn't have "draw" command */
8442             cps->sendDrawOffers = 0;
8443             return;
8444         }
8445         if (cps->sendTime != 1 &&
8446             (StrStr(message, "time") || StrStr(message, "otim"))) {
8447           /* Program apparently doesn't have "time" or "otim" command */
8448           cps->sendTime = 0;
8449           return;
8450         }
8451         if (StrStr(message, "analyze")) {
8452             cps->analysisSupport = FALSE;
8453             cps->analyzing = FALSE;
8454 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8455             EditGameEvent(); // [HGM] try to preserve loaded game
8456             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8457             DisplayError(buf2, 0);
8458             return;
8459         }
8460         if (StrStr(message, "(no matching move)st")) {
8461           /* Special kludge for GNU Chess 4 only */
8462           cps->stKludge = TRUE;
8463           SendTimeControl(cps, movesPerSession, timeControl,
8464                           timeIncrement, appData.searchDepth,
8465                           searchTime);
8466           return;
8467         }
8468         if (StrStr(message, "(no matching move)sd")) {
8469           /* Special kludge for GNU Chess 4 only */
8470           cps->sdKludge = TRUE;
8471           SendTimeControl(cps, movesPerSession, timeControl,
8472                           timeIncrement, appData.searchDepth,
8473                           searchTime);
8474           return;
8475         }
8476         if (!StrStr(message, "llegal")) {
8477             return;
8478         }
8479         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8480             gameMode == IcsIdle) return;
8481         if (forwardMostMove <= backwardMostMove) return;
8482         if (pausing) PauseEvent();
8483       if(appData.forceIllegal) {
8484             // [HGM] illegal: machine refused move; force position after move into it
8485           SendToProgram("force\n", cps);
8486           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8487                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8488                 // when black is to move, while there might be nothing on a2 or black
8489                 // might already have the move. So send the board as if white has the move.
8490                 // But first we must change the stm of the engine, as it refused the last move
8491                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8492                 if(WhiteOnMove(forwardMostMove)) {
8493                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8494                     SendBoard(cps, forwardMostMove); // kludgeless board
8495                 } else {
8496                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8497                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8498                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8499                 }
8500           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8501             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8502                  gameMode == TwoMachinesPlay)
8503               SendToProgram("go\n", cps);
8504             return;
8505       } else
8506         if (gameMode == PlayFromGameFile) {
8507             /* Stop reading this game file */
8508             gameMode = EditGame;
8509             ModeHighlight();
8510         }
8511         /* [HGM] illegal-move claim should forfeit game when Xboard */
8512         /* only passes fully legal moves                            */
8513         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8514             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8515                                 "False illegal-move claim", GE_XBOARD );
8516             return; // do not take back move we tested as valid
8517         }
8518         currentMove = forwardMostMove-1;
8519         DisplayMove(currentMove-1); /* before DisplayMoveError */
8520         SwitchClocks(forwardMostMove-1); // [HGM] race
8521         DisplayBothClocks();
8522         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8523                 parseList[currentMove], _(cps->which));
8524         DisplayMoveError(buf1);
8525         DrawPosition(FALSE, boards[currentMove]);
8526
8527         SetUserThinkingEnables();
8528         return;
8529     }
8530     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8531         /* Program has a broken "time" command that
8532            outputs a string not ending in newline.
8533            Don't use it. */
8534         cps->sendTime = 0;
8535     }
8536
8537     /*
8538      * If chess program startup fails, exit with an error message.
8539      * Attempts to recover here are futile. [HGM] Well, we try anyway
8540      */
8541     if ((StrStr(message, "unknown host") != NULL)
8542         || (StrStr(message, "No remote directory") != NULL)
8543         || (StrStr(message, "not found") != NULL)
8544         || (StrStr(message, "No such file") != NULL)
8545         || (StrStr(message, "can't alloc") != NULL)
8546         || (StrStr(message, "Permission denied") != NULL)) {
8547
8548         cps->maybeThinking = FALSE;
8549         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8550                 _(cps->which), cps->program, cps->host, message);
8551         RemoveInputSource(cps->isr);
8552         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8553             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8554             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8555         }
8556         return;
8557     }
8558
8559     /*
8560      * Look for hint output
8561      */
8562     if (sscanf(message, "Hint: %s", buf1) == 1) {
8563         if (cps == &first && hintRequested) {
8564             hintRequested = FALSE;
8565             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8566                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8567                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8568                                     PosFlags(forwardMostMove),
8569                                     fromY, fromX, toY, toX, promoChar, buf1);
8570                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8571                 DisplayInformation(buf2);
8572             } else {
8573                 /* Hint move could not be parsed!? */
8574               snprintf(buf2, sizeof(buf2),
8575                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8576                         buf1, _(cps->which));
8577                 DisplayError(buf2, 0);
8578             }
8579         } else {
8580           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8581         }
8582         return;
8583     }
8584
8585     /*
8586      * Ignore other messages if game is not in progress
8587      */
8588     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8589         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8590
8591     /*
8592      * look for win, lose, draw, or draw offer
8593      */
8594     if (strncmp(message, "1-0", 3) == 0) {
8595         char *p, *q, *r = "";
8596         p = strchr(message, '{');
8597         if (p) {
8598             q = strchr(p, '}');
8599             if (q) {
8600                 *q = NULLCHAR;
8601                 r = p + 1;
8602             }
8603         }
8604         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8605         return;
8606     } else if (strncmp(message, "0-1", 3) == 0) {
8607         char *p, *q, *r = "";
8608         p = strchr(message, '{');
8609         if (p) {
8610             q = strchr(p, '}');
8611             if (q) {
8612                 *q = NULLCHAR;
8613                 r = p + 1;
8614             }
8615         }
8616         /* Kludge for Arasan 4.1 bug */
8617         if (strcmp(r, "Black resigns") == 0) {
8618             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8619             return;
8620         }
8621         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8622         return;
8623     } else if (strncmp(message, "1/2", 3) == 0) {
8624         char *p, *q, *r = "";
8625         p = strchr(message, '{');
8626         if (p) {
8627             q = strchr(p, '}');
8628             if (q) {
8629                 *q = NULLCHAR;
8630                 r = p + 1;
8631             }
8632         }
8633
8634         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8635         return;
8636
8637     } else if (strncmp(message, "White resign", 12) == 0) {
8638         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8639         return;
8640     } else if (strncmp(message, "Black resign", 12) == 0) {
8641         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8642         return;
8643     } else if (strncmp(message, "White matches", 13) == 0 ||
8644                strncmp(message, "Black matches", 13) == 0   ) {
8645         /* [HGM] ignore GNUShogi noises */
8646         return;
8647     } else if (strncmp(message, "White", 5) == 0 &&
8648                message[5] != '(' &&
8649                StrStr(message, "Black") == NULL) {
8650         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8651         return;
8652     } else if (strncmp(message, "Black", 5) == 0 &&
8653                message[5] != '(') {
8654         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8655         return;
8656     } else if (strcmp(message, "resign") == 0 ||
8657                strcmp(message, "computer resigns") == 0) {
8658         switch (gameMode) {
8659           case MachinePlaysBlack:
8660           case IcsPlayingBlack:
8661             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8662             break;
8663           case MachinePlaysWhite:
8664           case IcsPlayingWhite:
8665             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8666             break;
8667           case TwoMachinesPlay:
8668             if (cps->twoMachinesColor[0] == 'w')
8669               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8670             else
8671               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8672             break;
8673           default:
8674             /* can't happen */
8675             break;
8676         }
8677         return;
8678     } else if (strncmp(message, "opponent mates", 14) == 0) {
8679         switch (gameMode) {
8680           case MachinePlaysBlack:
8681           case IcsPlayingBlack:
8682             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8683             break;
8684           case MachinePlaysWhite:
8685           case IcsPlayingWhite:
8686             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8687             break;
8688           case TwoMachinesPlay:
8689             if (cps->twoMachinesColor[0] == 'w')
8690               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8691             else
8692               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8693             break;
8694           default:
8695             /* can't happen */
8696             break;
8697         }
8698         return;
8699     } else if (strncmp(message, "computer mates", 14) == 0) {
8700         switch (gameMode) {
8701           case MachinePlaysBlack:
8702           case IcsPlayingBlack:
8703             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8704             break;
8705           case MachinePlaysWhite:
8706           case IcsPlayingWhite:
8707             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8708             break;
8709           case TwoMachinesPlay:
8710             if (cps->twoMachinesColor[0] == 'w')
8711               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8712             else
8713               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8714             break;
8715           default:
8716             /* can't happen */
8717             break;
8718         }
8719         return;
8720     } else if (strncmp(message, "checkmate", 9) == 0) {
8721         if (WhiteOnMove(forwardMostMove)) {
8722             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8723         } else {
8724             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8725         }
8726         return;
8727     } else if (strstr(message, "Draw") != NULL ||
8728                strstr(message, "game is a draw") != NULL) {
8729         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8730         return;
8731     } else if (strstr(message, "offer") != NULL &&
8732                strstr(message, "draw") != NULL) {
8733 #if ZIPPY
8734         if (appData.zippyPlay && first.initDone) {
8735             /* Relay offer to ICS */
8736             SendToICS(ics_prefix);
8737             SendToICS("draw\n");
8738         }
8739 #endif
8740         cps->offeredDraw = 2; /* valid until this engine moves twice */
8741         if (gameMode == TwoMachinesPlay) {
8742             if (cps->other->offeredDraw) {
8743                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8744             /* [HGM] in two-machine mode we delay relaying draw offer      */
8745             /* until after we also have move, to see if it is really claim */
8746             }
8747         } else if (gameMode == MachinePlaysWhite ||
8748                    gameMode == MachinePlaysBlack) {
8749           if (userOfferedDraw) {
8750             DisplayInformation(_("Machine accepts your draw offer"));
8751             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8752           } else {
8753             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8754           }
8755         }
8756     }
8757
8758
8759     /*
8760      * Look for thinking output
8761      */
8762     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8763           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8764                                 ) {
8765         int plylev, mvleft, mvtot, curscore, time;
8766         char mvname[MOVE_LEN];
8767         u64 nodes; // [DM]
8768         char plyext;
8769         int ignore = FALSE;
8770         int prefixHint = FALSE;
8771         mvname[0] = NULLCHAR;
8772
8773         switch (gameMode) {
8774           case MachinePlaysBlack:
8775           case IcsPlayingBlack:
8776             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8777             break;
8778           case MachinePlaysWhite:
8779           case IcsPlayingWhite:
8780             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8781             break;
8782           case AnalyzeMode:
8783           case AnalyzeFile:
8784             break;
8785           case IcsObserving: /* [DM] icsEngineAnalyze */
8786             if (!appData.icsEngineAnalyze) ignore = TRUE;
8787             break;
8788           case TwoMachinesPlay:
8789             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8790                 ignore = TRUE;
8791             }
8792             break;
8793           default:
8794             ignore = TRUE;
8795             break;
8796         }
8797
8798         if (!ignore) {
8799             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8800             buf1[0] = NULLCHAR;
8801             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8802                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8803
8804                 if (plyext != ' ' && plyext != '\t') {
8805                     time *= 100;
8806                 }
8807
8808                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8809                 if( cps->scoreIsAbsolute &&
8810                     ( gameMode == MachinePlaysBlack ||
8811                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8812                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8813                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8814                      !WhiteOnMove(currentMove)
8815                     ) )
8816                 {
8817                     curscore = -curscore;
8818                 }
8819
8820                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8821
8822                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8823                         char buf[MSG_SIZ];
8824                         FILE *f;
8825                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8826                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8827                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8828                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8829                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8830                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8831                                 fclose(f);
8832                         } else DisplayError(_("failed writing PV"), 0);
8833                 }
8834
8835                 tempStats.depth = plylev;
8836                 tempStats.nodes = nodes;
8837                 tempStats.time = time;
8838                 tempStats.score = curscore;
8839                 tempStats.got_only_move = 0;
8840
8841                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8842                         int ticklen;
8843
8844                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8845                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8846                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8847                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8848                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8849                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8850                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8851                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8852                 }
8853
8854                 /* Buffer overflow protection */
8855                 if (pv[0] != NULLCHAR) {
8856                     if (strlen(pv) >= sizeof(tempStats.movelist)
8857                         && appData.debugMode) {
8858                         fprintf(debugFP,
8859                                 "PV is too long; using the first %u bytes.\n",
8860                                 (unsigned) sizeof(tempStats.movelist) - 1);
8861                     }
8862
8863                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8864                 } else {
8865                     sprintf(tempStats.movelist, " no PV\n");
8866                 }
8867
8868                 if (tempStats.seen_stat) {
8869                     tempStats.ok_to_send = 1;
8870                 }
8871
8872                 if (strchr(tempStats.movelist, '(') != NULL) {
8873                     tempStats.line_is_book = 1;
8874                     tempStats.nr_moves = 0;
8875                     tempStats.moves_left = 0;
8876                 } else {
8877                     tempStats.line_is_book = 0;
8878                 }
8879
8880                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8881                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8882
8883                 SendProgramStatsToFrontend( cps, &tempStats );
8884
8885                 /*
8886                     [AS] Protect the thinkOutput buffer from overflow... this
8887                     is only useful if buf1 hasn't overflowed first!
8888                 */
8889                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8890                          plylev,
8891                          (gameMode == TwoMachinesPlay ?
8892                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8893                          ((double) curscore) / 100.0,
8894                          prefixHint ? lastHint : "",
8895                          prefixHint ? " " : "" );
8896
8897                 if( buf1[0] != NULLCHAR ) {
8898                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8899
8900                     if( strlen(pv) > max_len ) {
8901                         if( appData.debugMode) {
8902                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8903                         }
8904                         pv[max_len+1] = '\0';
8905                     }
8906
8907                     strcat( thinkOutput, pv);
8908                 }
8909
8910                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8911                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8912                     DisplayMove(currentMove - 1);
8913                 }
8914                 return;
8915
8916             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8917                 /* crafty (9.25+) says "(only move) <move>"
8918                  * if there is only 1 legal move
8919                  */
8920                 sscanf(p, "(only move) %s", buf1);
8921                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8922                 sprintf(programStats.movelist, "%s (only move)", buf1);
8923                 programStats.depth = 1;
8924                 programStats.nr_moves = 1;
8925                 programStats.moves_left = 1;
8926                 programStats.nodes = 1;
8927                 programStats.time = 1;
8928                 programStats.got_only_move = 1;
8929
8930                 /* Not really, but we also use this member to
8931                    mean "line isn't going to change" (Crafty
8932                    isn't searching, so stats won't change) */
8933                 programStats.line_is_book = 1;
8934
8935                 SendProgramStatsToFrontend( cps, &programStats );
8936
8937                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8938                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8939                     DisplayMove(currentMove - 1);
8940                 }
8941                 return;
8942             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8943                               &time, &nodes, &plylev, &mvleft,
8944                               &mvtot, mvname) >= 5) {
8945                 /* The stat01: line is from Crafty (9.29+) in response
8946                    to the "." command */
8947                 programStats.seen_stat = 1;
8948                 cps->maybeThinking = TRUE;
8949
8950                 if (programStats.got_only_move || !appData.periodicUpdates)
8951                   return;
8952
8953                 programStats.depth = plylev;
8954                 programStats.time = time;
8955                 programStats.nodes = nodes;
8956                 programStats.moves_left = mvleft;
8957                 programStats.nr_moves = mvtot;
8958                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8959                 programStats.ok_to_send = 1;
8960                 programStats.movelist[0] = '\0';
8961
8962                 SendProgramStatsToFrontend( cps, &programStats );
8963
8964                 return;
8965
8966             } else if (strncmp(message,"++",2) == 0) {
8967                 /* Crafty 9.29+ outputs this */
8968                 programStats.got_fail = 2;
8969                 return;
8970
8971             } else if (strncmp(message,"--",2) == 0) {
8972                 /* Crafty 9.29+ outputs this */
8973                 programStats.got_fail = 1;
8974                 return;
8975
8976             } else if (thinkOutput[0] != NULLCHAR &&
8977                        strncmp(message, "    ", 4) == 0) {
8978                 unsigned message_len;
8979
8980                 p = message;
8981                 while (*p && *p == ' ') p++;
8982
8983                 message_len = strlen( p );
8984
8985                 /* [AS] Avoid buffer overflow */
8986                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8987                     strcat(thinkOutput, " ");
8988                     strcat(thinkOutput, p);
8989                 }
8990
8991                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8992                     strcat(programStats.movelist, " ");
8993                     strcat(programStats.movelist, p);
8994                 }
8995
8996                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8997                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8998                     DisplayMove(currentMove - 1);
8999                 }
9000                 return;
9001             }
9002         }
9003         else {
9004             buf1[0] = NULLCHAR;
9005
9006             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9007                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9008             {
9009                 ChessProgramStats cpstats;
9010
9011                 if (plyext != ' ' && plyext != '\t') {
9012                     time *= 100;
9013                 }
9014
9015                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9016                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9017                     curscore = -curscore;
9018                 }
9019
9020                 cpstats.depth = plylev;
9021                 cpstats.nodes = nodes;
9022                 cpstats.time = time;
9023                 cpstats.score = curscore;
9024                 cpstats.got_only_move = 0;
9025                 cpstats.movelist[0] = '\0';
9026
9027                 if (buf1[0] != NULLCHAR) {
9028                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9029                 }
9030
9031                 cpstats.ok_to_send = 0;
9032                 cpstats.line_is_book = 0;
9033                 cpstats.nr_moves = 0;
9034                 cpstats.moves_left = 0;
9035
9036                 SendProgramStatsToFrontend( cps, &cpstats );
9037             }
9038         }
9039     }
9040 }
9041
9042
9043 /* Parse a game score from the character string "game", and
9044    record it as the history of the current game.  The game
9045    score is NOT assumed to start from the standard position.
9046    The display is not updated in any way.
9047    */
9048 void
9049 ParseGameHistory (char *game)
9050 {
9051     ChessMove moveType;
9052     int fromX, fromY, toX, toY, boardIndex;
9053     char promoChar;
9054     char *p, *q;
9055     char buf[MSG_SIZ];
9056
9057     if (appData.debugMode)
9058       fprintf(debugFP, "Parsing game history: %s\n", game);
9059
9060     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9061     gameInfo.site = StrSave(appData.icsHost);
9062     gameInfo.date = PGNDate();
9063     gameInfo.round = StrSave("-");
9064
9065     /* Parse out names of players */
9066     while (*game == ' ') game++;
9067     p = buf;
9068     while (*game != ' ') *p++ = *game++;
9069     *p = NULLCHAR;
9070     gameInfo.white = StrSave(buf);
9071     while (*game == ' ') game++;
9072     p = buf;
9073     while (*game != ' ' && *game != '\n') *p++ = *game++;
9074     *p = NULLCHAR;
9075     gameInfo.black = StrSave(buf);
9076
9077     /* Parse moves */
9078     boardIndex = blackPlaysFirst ? 1 : 0;
9079     yynewstr(game);
9080     for (;;) {
9081         yyboardindex = boardIndex;
9082         moveType = (ChessMove) Myylex();
9083         switch (moveType) {
9084           case IllegalMove:             /* maybe suicide chess, etc. */
9085   if (appData.debugMode) {
9086     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9087     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9088     setbuf(debugFP, NULL);
9089   }
9090           case WhitePromotion:
9091           case BlackPromotion:
9092           case WhiteNonPromotion:
9093           case BlackNonPromotion:
9094           case NormalMove:
9095           case WhiteCapturesEnPassant:
9096           case BlackCapturesEnPassant:
9097           case WhiteKingSideCastle:
9098           case WhiteQueenSideCastle:
9099           case BlackKingSideCastle:
9100           case BlackQueenSideCastle:
9101           case WhiteKingSideCastleWild:
9102           case WhiteQueenSideCastleWild:
9103           case BlackKingSideCastleWild:
9104           case BlackQueenSideCastleWild:
9105           /* PUSH Fabien */
9106           case WhiteHSideCastleFR:
9107           case WhiteASideCastleFR:
9108           case BlackHSideCastleFR:
9109           case BlackASideCastleFR:
9110           /* POP Fabien */
9111             fromX = currentMoveString[0] - AAA;
9112             fromY = currentMoveString[1] - ONE;
9113             toX = currentMoveString[2] - AAA;
9114             toY = currentMoveString[3] - ONE;
9115             promoChar = currentMoveString[4];
9116             break;
9117           case WhiteDrop:
9118           case BlackDrop:
9119             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9120             fromX = moveType == WhiteDrop ?
9121               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9122             (int) CharToPiece(ToLower(currentMoveString[0]));
9123             fromY = DROP_RANK;
9124             toX = currentMoveString[2] - AAA;
9125             toY = currentMoveString[3] - ONE;
9126             promoChar = NULLCHAR;
9127             break;
9128           case AmbiguousMove:
9129             /* bug? */
9130             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9131   if (appData.debugMode) {
9132     fprintf(debugFP, "Ambiguous 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 ImpossibleMove:
9139             /* bug? */
9140             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9141   if (appData.debugMode) {
9142     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9143     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9144     setbuf(debugFP, NULL);
9145   }
9146             DisplayError(buf, 0);
9147             return;
9148           case EndOfFile:
9149             if (boardIndex < backwardMostMove) {
9150                 /* Oops, gap.  How did that happen? */
9151                 DisplayError(_("Gap in move list"), 0);
9152                 return;
9153             }
9154             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9155             if (boardIndex > forwardMostMove) {
9156                 forwardMostMove = boardIndex;
9157             }
9158             return;
9159           case ElapsedTime:
9160             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9161                 strcat(parseList[boardIndex-1], " ");
9162                 strcat(parseList[boardIndex-1], yy_text);
9163             }
9164             continue;
9165           case Comment:
9166           case PGNTag:
9167           case NAG:
9168           default:
9169             /* ignore */
9170             continue;
9171           case WhiteWins:
9172           case BlackWins:
9173           case GameIsDrawn:
9174           case GameUnfinished:
9175             if (gameMode == IcsExamining) {
9176                 if (boardIndex < backwardMostMove) {
9177                     /* Oops, gap.  How did that happen? */
9178                     return;
9179                 }
9180                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9181                 return;
9182             }
9183             gameInfo.result = moveType;
9184             p = strchr(yy_text, '{');
9185             if (p == NULL) p = strchr(yy_text, '(');
9186             if (p == NULL) {
9187                 p = yy_text;
9188                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9189             } else {
9190                 q = strchr(p, *p == '{' ? '}' : ')');
9191                 if (q != NULL) *q = NULLCHAR;
9192                 p++;
9193             }
9194             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9195             gameInfo.resultDetails = StrSave(p);
9196             continue;
9197         }
9198         if (boardIndex >= forwardMostMove &&
9199             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9200             backwardMostMove = blackPlaysFirst ? 1 : 0;
9201             return;
9202         }
9203         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9204                                  fromY, fromX, toY, toX, promoChar,
9205                                  parseList[boardIndex]);
9206         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9207         /* currentMoveString is set as a side-effect of yylex */
9208         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9209         strcat(moveList[boardIndex], "\n");
9210         boardIndex++;
9211         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9212         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9213           case MT_NONE:
9214           case MT_STALEMATE:
9215           default:
9216             break;
9217           case MT_CHECK:
9218             if(gameInfo.variant != VariantShogi)
9219                 strcat(parseList[boardIndex - 1], "+");
9220             break;
9221           case MT_CHECKMATE:
9222           case MT_STAINMATE:
9223             strcat(parseList[boardIndex - 1], "#");
9224             break;
9225         }
9226     }
9227 }
9228
9229
9230 /* Apply a move to the given board  */
9231 void
9232 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9233 {
9234   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9235   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9236
9237     /* [HGM] compute & store e.p. status and castling rights for new position */
9238     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9239
9240       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9241       oldEP = (signed char)board[EP_STATUS];
9242       board[EP_STATUS] = EP_NONE;
9243
9244   if (fromY == DROP_RANK) {
9245         /* must be first */
9246         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9247             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9248             return;
9249         }
9250         piece = board[toY][toX] = (ChessSquare) fromX;
9251   } else {
9252       int i;
9253
9254       if( board[toY][toX] != EmptySquare )
9255            board[EP_STATUS] = EP_CAPTURE;
9256
9257       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9258            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9259                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9260       } else
9261       if( board[fromY][fromX] == WhitePawn ) {
9262            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9263                board[EP_STATUS] = EP_PAWN_MOVE;
9264            if( toY-fromY==2) {
9265                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9266                         gameInfo.variant != VariantBerolina || toX < fromX)
9267                       board[EP_STATUS] = toX | berolina;
9268                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9269                         gameInfo.variant != VariantBerolina || toX > fromX)
9270                       board[EP_STATUS] = toX;
9271            }
9272       } else
9273       if( board[fromY][fromX] == BlackPawn ) {
9274            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9275                board[EP_STATUS] = EP_PAWN_MOVE;
9276            if( toY-fromY== -2) {
9277                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9278                         gameInfo.variant != VariantBerolina || toX < fromX)
9279                       board[EP_STATUS] = toX | berolina;
9280                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9281                         gameInfo.variant != VariantBerolina || toX > fromX)
9282                       board[EP_STATUS] = toX;
9283            }
9284        }
9285
9286        for(i=0; i<nrCastlingRights; i++) {
9287            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9288               board[CASTLING][i] == toX   && castlingRank[i] == toY
9289              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9290        }
9291
9292      if (fromX == toX && fromY == toY) return;
9293
9294      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9295      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9296      if(gameInfo.variant == VariantKnightmate)
9297          king += (int) WhiteUnicorn - (int) WhiteKing;
9298
9299     /* Code added by Tord: */
9300     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9301     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9302         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9303       board[fromY][fromX] = EmptySquare;
9304       board[toY][toX] = EmptySquare;
9305       if((toX > fromX) != (piece == WhiteRook)) {
9306         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9307       } else {
9308         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9309       }
9310     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9311                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9312       board[fromY][fromX] = EmptySquare;
9313       board[toY][toX] = EmptySquare;
9314       if((toX > fromX) != (piece == BlackRook)) {
9315         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9316       } else {
9317         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9318       }
9319     /* End of code added by Tord */
9320
9321     } else if (board[fromY][fromX] == king
9322         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9323         && toY == fromY && toX > fromX+1) {
9324         board[fromY][fromX] = EmptySquare;
9325         board[toY][toX] = king;
9326         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9327         board[fromY][BOARD_RGHT-1] = EmptySquare;
9328     } else if (board[fromY][fromX] == king
9329         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9330                && toY == fromY && toX < fromX-1) {
9331         board[fromY][fromX] = EmptySquare;
9332         board[toY][toX] = king;
9333         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9334         board[fromY][BOARD_LEFT] = EmptySquare;
9335     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9336                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9337                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9338                ) {
9339         /* white pawn promotion */
9340         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9341         if(gameInfo.variant==VariantBughouse ||
9342            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9343             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9344         board[fromY][fromX] = EmptySquare;
9345     } else if ((fromY >= BOARD_HEIGHT>>1)
9346                && (toX != fromX)
9347                && gameInfo.variant != VariantXiangqi
9348                && gameInfo.variant != VariantBerolina
9349                && (board[fromY][fromX] == WhitePawn)
9350                && (board[toY][toX] == EmptySquare)) {
9351         board[fromY][fromX] = EmptySquare;
9352         board[toY][toX] = WhitePawn;
9353         captured = board[toY - 1][toX];
9354         board[toY - 1][toX] = EmptySquare;
9355     } else if ((fromY == BOARD_HEIGHT-4)
9356                && (toX == fromX)
9357                && gameInfo.variant == VariantBerolina
9358                && (board[fromY][fromX] == WhitePawn)
9359                && (board[toY][toX] == EmptySquare)) {
9360         board[fromY][fromX] = EmptySquare;
9361         board[toY][toX] = WhitePawn;
9362         if(oldEP & EP_BEROLIN_A) {
9363                 captured = board[fromY][fromX-1];
9364                 board[fromY][fromX-1] = EmptySquare;
9365         }else{  captured = board[fromY][fromX+1];
9366                 board[fromY][fromX+1] = EmptySquare;
9367         }
9368     } else if (board[fromY][fromX] == king
9369         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9370                && toY == fromY && toX > fromX+1) {
9371         board[fromY][fromX] = EmptySquare;
9372         board[toY][toX] = king;
9373         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9374         board[fromY][BOARD_RGHT-1] = EmptySquare;
9375     } else if (board[fromY][fromX] == king
9376         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9377                && toY == fromY && toX < fromX-1) {
9378         board[fromY][fromX] = EmptySquare;
9379         board[toY][toX] = king;
9380         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9381         board[fromY][BOARD_LEFT] = EmptySquare;
9382     } else if (fromY == 7 && fromX == 3
9383                && board[fromY][fromX] == BlackKing
9384                && toY == 7 && toX == 5) {
9385         board[fromY][fromX] = EmptySquare;
9386         board[toY][toX] = BlackKing;
9387         board[fromY][7] = EmptySquare;
9388         board[toY][4] = BlackRook;
9389     } else if (fromY == 7 && fromX == 3
9390                && board[fromY][fromX] == BlackKing
9391                && toY == 7 && toX == 1) {
9392         board[fromY][fromX] = EmptySquare;
9393         board[toY][toX] = BlackKing;
9394         board[fromY][0] = EmptySquare;
9395         board[toY][2] = BlackRook;
9396     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9397                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9398                && toY < promoRank && promoChar
9399                ) {
9400         /* black pawn promotion */
9401         board[toY][toX] = CharToPiece(ToLower(promoChar));
9402         if(gameInfo.variant==VariantBughouse ||
9403            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9404             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9405         board[fromY][fromX] = EmptySquare;
9406     } else if ((fromY < BOARD_HEIGHT>>1)
9407                && (toX != fromX)
9408                && gameInfo.variant != VariantXiangqi
9409                && gameInfo.variant != VariantBerolina
9410                && (board[fromY][fromX] == BlackPawn)
9411                && (board[toY][toX] == EmptySquare)) {
9412         board[fromY][fromX] = EmptySquare;
9413         board[toY][toX] = BlackPawn;
9414         captured = board[toY + 1][toX];
9415         board[toY + 1][toX] = EmptySquare;
9416     } else if ((fromY == 3)
9417                && (toX == fromX)
9418                && gameInfo.variant == VariantBerolina
9419                && (board[fromY][fromX] == BlackPawn)
9420                && (board[toY][toX] == EmptySquare)) {
9421         board[fromY][fromX] = EmptySquare;
9422         board[toY][toX] = BlackPawn;
9423         if(oldEP & EP_BEROLIN_A) {
9424                 captured = board[fromY][fromX-1];
9425                 board[fromY][fromX-1] = EmptySquare;
9426         }else{  captured = board[fromY][fromX+1];
9427                 board[fromY][fromX+1] = EmptySquare;
9428         }
9429     } else {
9430         board[toY][toX] = board[fromY][fromX];
9431         board[fromY][fromX] = EmptySquare;
9432     }
9433   }
9434
9435     if (gameInfo.holdingsWidth != 0) {
9436
9437       /* !!A lot more code needs to be written to support holdings  */
9438       /* [HGM] OK, so I have written it. Holdings are stored in the */
9439       /* penultimate board files, so they are automaticlly stored   */
9440       /* in the game history.                                       */
9441       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9442                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9443         /* Delete from holdings, by decreasing count */
9444         /* and erasing image if necessary            */
9445         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9446         if(p < (int) BlackPawn) { /* white drop */
9447              p -= (int)WhitePawn;
9448                  p = PieceToNumber((ChessSquare)p);
9449              if(p >= gameInfo.holdingsSize) p = 0;
9450              if(--board[p][BOARD_WIDTH-2] <= 0)
9451                   board[p][BOARD_WIDTH-1] = EmptySquare;
9452              if((int)board[p][BOARD_WIDTH-2] < 0)
9453                         board[p][BOARD_WIDTH-2] = 0;
9454         } else {                  /* black drop */
9455              p -= (int)BlackPawn;
9456                  p = PieceToNumber((ChessSquare)p);
9457              if(p >= gameInfo.holdingsSize) p = 0;
9458              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9459                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9460              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9461                         board[BOARD_HEIGHT-1-p][1] = 0;
9462         }
9463       }
9464       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9465           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9466         /* [HGM] holdings: Add to holdings, if holdings exist */
9467         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9468                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9469                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9470         }
9471         p = (int) captured;
9472         if (p >= (int) BlackPawn) {
9473           p -= (int)BlackPawn;
9474           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9475                   /* in Shogi restore piece to its original  first */
9476                   captured = (ChessSquare) (DEMOTED captured);
9477                   p = DEMOTED p;
9478           }
9479           p = PieceToNumber((ChessSquare)p);
9480           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9481           board[p][BOARD_WIDTH-2]++;
9482           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9483         } else {
9484           p -= (int)WhitePawn;
9485           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9486                   captured = (ChessSquare) (DEMOTED captured);
9487                   p = DEMOTED p;
9488           }
9489           p = PieceToNumber((ChessSquare)p);
9490           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9491           board[BOARD_HEIGHT-1-p][1]++;
9492           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9493         }
9494       }
9495     } else if (gameInfo.variant == VariantAtomic) {
9496       if (captured != EmptySquare) {
9497         int y, x;
9498         for (y = toY-1; y <= toY+1; y++) {
9499           for (x = toX-1; x <= toX+1; x++) {
9500             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9501                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9502               board[y][x] = EmptySquare;
9503             }
9504           }
9505         }
9506         board[toY][toX] = EmptySquare;
9507       }
9508     }
9509     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9510         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9511     } else
9512     if(promoChar == '+') {
9513         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9514         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9515     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9516         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9517         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9518            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9519         board[toY][toX] = newPiece;
9520     }
9521     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9522                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9523         // [HGM] superchess: take promotion piece out of holdings
9524         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9525         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9526             if(!--board[k][BOARD_WIDTH-2])
9527                 board[k][BOARD_WIDTH-1] = EmptySquare;
9528         } else {
9529             if(!--board[BOARD_HEIGHT-1-k][1])
9530                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9531         }
9532     }
9533
9534 }
9535
9536 /* Updates forwardMostMove */
9537 void
9538 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9539 {
9540 //    forwardMostMove++; // [HGM] bare: moved downstream
9541
9542     (void) CoordsToAlgebraic(boards[forwardMostMove],
9543                              PosFlags(forwardMostMove),
9544                              fromY, fromX, toY, toX, promoChar,
9545                              parseList[forwardMostMove]);
9546
9547     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9548         int timeLeft; static int lastLoadFlag=0; int king, piece;
9549         piece = boards[forwardMostMove][fromY][fromX];
9550         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9551         if(gameInfo.variant == VariantKnightmate)
9552             king += (int) WhiteUnicorn - (int) WhiteKing;
9553         if(forwardMostMove == 0) {
9554             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9555                 fprintf(serverMoves, "%s;", UserName());
9556             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9557                 fprintf(serverMoves, "%s;", second.tidy);
9558             fprintf(serverMoves, "%s;", first.tidy);
9559             if(gameMode == MachinePlaysWhite)
9560                 fprintf(serverMoves, "%s;", UserName());
9561             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9562                 fprintf(serverMoves, "%s;", second.tidy);
9563         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9564         lastLoadFlag = loadFlag;
9565         // print base move
9566         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9567         // print castling suffix
9568         if( toY == fromY && piece == king ) {
9569             if(toX-fromX > 1)
9570                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9571             if(fromX-toX >1)
9572                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9573         }
9574         // e.p. suffix
9575         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9576              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9577              boards[forwardMostMove][toY][toX] == EmptySquare
9578              && fromX != toX && fromY != toY)
9579                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9580         // promotion suffix
9581         if(promoChar != NULLCHAR)
9582                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9583         if(!loadFlag) {
9584                 char buf[MOVE_LEN*2], *p; int len;
9585             fprintf(serverMoves, "/%d/%d",
9586                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9587             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9588             else                      timeLeft = blackTimeRemaining/1000;
9589             fprintf(serverMoves, "/%d", timeLeft);
9590                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9591                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9592                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9593             fprintf(serverMoves, "/%s", buf);
9594         }
9595         fflush(serverMoves);
9596     }
9597
9598     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9599         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9600       return;
9601     }
9602     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9603     if (commentList[forwardMostMove+1] != NULL) {
9604         free(commentList[forwardMostMove+1]);
9605         commentList[forwardMostMove+1] = NULL;
9606     }
9607     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9608     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9609     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9610     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9611     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9612     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9613     adjustedClock = FALSE;
9614     gameInfo.result = GameUnfinished;
9615     if (gameInfo.resultDetails != NULL) {
9616         free(gameInfo.resultDetails);
9617         gameInfo.resultDetails = NULL;
9618     }
9619     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9620                               moveList[forwardMostMove - 1]);
9621     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9622       case MT_NONE:
9623       case MT_STALEMATE:
9624       default:
9625         break;
9626       case MT_CHECK:
9627         if(gameInfo.variant != VariantShogi)
9628             strcat(parseList[forwardMostMove - 1], "+");
9629         break;
9630       case MT_CHECKMATE:
9631       case MT_STAINMATE:
9632         strcat(parseList[forwardMostMove - 1], "#");
9633         break;
9634     }
9635
9636 }
9637
9638 /* Updates currentMove if not pausing */
9639 void
9640 ShowMove (int fromX, int fromY, int toX, int toY)
9641 {
9642     int instant = (gameMode == PlayFromGameFile) ?
9643         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9644     if(appData.noGUI) return;
9645     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9646         if (!instant) {
9647             if (forwardMostMove == currentMove + 1) {
9648                 AnimateMove(boards[forwardMostMove - 1],
9649                             fromX, fromY, toX, toY);
9650             }
9651             if (appData.highlightLastMove) {
9652                 SetHighlights(fromX, fromY, toX, toY);
9653             }
9654         }
9655         currentMove = forwardMostMove;
9656     }
9657
9658     if (instant) return;
9659
9660     DisplayMove(currentMove - 1);
9661     DrawPosition(FALSE, boards[currentMove]);
9662     DisplayBothClocks();
9663     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9664 }
9665
9666 void
9667 SendEgtPath (ChessProgramState *cps)
9668 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9669         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9670
9671         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9672
9673         while(*p) {
9674             char c, *q = name+1, *r, *s;
9675
9676             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9677             while(*p && *p != ',') *q++ = *p++;
9678             *q++ = ':'; *q = 0;
9679             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9680                 strcmp(name, ",nalimov:") == 0 ) {
9681                 // take nalimov path from the menu-changeable option first, if it is defined
9682               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9683                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9684             } else
9685             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9686                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9687                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9688                 s = r = StrStr(s, ":") + 1; // beginning of path info
9689                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9690                 c = *r; *r = 0;             // temporarily null-terminate path info
9691                     *--q = 0;               // strip of trailig ':' from name
9692                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9693                 *r = c;
9694                 SendToProgram(buf,cps);     // send egtbpath command for this format
9695             }
9696             if(*p == ',') p++; // read away comma to position for next format name
9697         }
9698 }
9699
9700 void
9701 InitChessProgram (ChessProgramState *cps, int setup)
9702 /* setup needed to setup FRC opening position */
9703 {
9704     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9705     if (appData.noChessProgram) return;
9706     hintRequested = FALSE;
9707     bookRequested = FALSE;
9708
9709     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9710     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9711     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9712     if(cps->memSize) { /* [HGM] memory */
9713       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9714         SendToProgram(buf, cps);
9715     }
9716     SendEgtPath(cps); /* [HGM] EGT */
9717     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9718       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9719         SendToProgram(buf, cps);
9720     }
9721
9722     SendToProgram(cps->initString, cps);
9723     if (gameInfo.variant != VariantNormal &&
9724         gameInfo.variant != VariantLoadable
9725         /* [HGM] also send variant if board size non-standard */
9726         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9727                                             ) {
9728       char *v = VariantName(gameInfo.variant);
9729       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9730         /* [HGM] in protocol 1 we have to assume all variants valid */
9731         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9732         DisplayFatalError(buf, 0, 1);
9733         return;
9734       }
9735
9736       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9737       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9738       if( gameInfo.variant == VariantXiangqi )
9739            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9740       if( gameInfo.variant == VariantShogi )
9741            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9742       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9743            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9744       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9745           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9746            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9747       if( gameInfo.variant == VariantCourier )
9748            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9749       if( gameInfo.variant == VariantSuper )
9750            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9751       if( gameInfo.variant == VariantGreat )
9752            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9753       if( gameInfo.variant == VariantSChess )
9754            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9755       if( gameInfo.variant == VariantGrand )
9756            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9757
9758       if(overruled) {
9759         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9760                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9761            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9762            if(StrStr(cps->variants, b) == NULL) {
9763                // specific sized variant not known, check if general sizing allowed
9764                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9765                    if(StrStr(cps->variants, "boardsize") == NULL) {
9766                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9767                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9768                        DisplayFatalError(buf, 0, 1);
9769                        return;
9770                    }
9771                    /* [HGM] here we really should compare with the maximum supported board size */
9772                }
9773            }
9774       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9775       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9776       SendToProgram(buf, cps);
9777     }
9778     currentlyInitializedVariant = gameInfo.variant;
9779
9780     /* [HGM] send opening position in FRC to first engine */
9781     if(setup) {
9782           SendToProgram("force\n", cps);
9783           SendBoard(cps, 0);
9784           /* engine is now in force mode! Set flag to wake it up after first move. */
9785           setboardSpoiledMachineBlack = 1;
9786     }
9787
9788     if (cps->sendICS) {
9789       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9790       SendToProgram(buf, cps);
9791     }
9792     cps->maybeThinking = FALSE;
9793     cps->offeredDraw = 0;
9794     if (!appData.icsActive) {
9795         SendTimeControl(cps, movesPerSession, timeControl,
9796                         timeIncrement, appData.searchDepth,
9797                         searchTime);
9798     }
9799     if (appData.showThinking
9800         // [HGM] thinking: four options require thinking output to be sent
9801         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9802                                 ) {
9803         SendToProgram("post\n", cps);
9804     }
9805     SendToProgram("hard\n", cps);
9806     if (!appData.ponderNextMove) {
9807         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9808            it without being sure what state we are in first.  "hard"
9809            is not a toggle, so that one is OK.
9810          */
9811         SendToProgram("easy\n", cps);
9812     }
9813     if (cps->usePing) {
9814       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9815       SendToProgram(buf, cps);
9816     }
9817     cps->initDone = TRUE;
9818     ClearEngineOutputPane(cps == &second);
9819 }
9820
9821
9822 void
9823 StartChessProgram (ChessProgramState *cps)
9824 {
9825     char buf[MSG_SIZ];
9826     int err;
9827
9828     if (appData.noChessProgram) return;
9829     cps->initDone = FALSE;
9830
9831     if (strcmp(cps->host, "localhost") == 0) {
9832         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9833     } else if (*appData.remoteShell == NULLCHAR) {
9834         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9835     } else {
9836         if (*appData.remoteUser == NULLCHAR) {
9837           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9838                     cps->program);
9839         } else {
9840           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9841                     cps->host, appData.remoteUser, cps->program);
9842         }
9843         err = StartChildProcess(buf, "", &cps->pr);
9844     }
9845
9846     if (err != 0) {
9847       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9848         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9849         if(cps != &first) return;
9850         appData.noChessProgram = TRUE;
9851         ThawUI();
9852         SetNCPMode();
9853 //      DisplayFatalError(buf, err, 1);
9854 //      cps->pr = NoProc;
9855 //      cps->isr = NULL;
9856         return;
9857     }
9858
9859     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9860     if (cps->protocolVersion > 1) {
9861       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9862       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9863       cps->comboCnt = 0;  //                and values of combo boxes
9864       SendToProgram(buf, cps);
9865     } else {
9866       SendToProgram("xboard\n", cps);
9867     }
9868 }
9869
9870 void
9871 TwoMachinesEventIfReady P((void))
9872 {
9873   static int curMess = 0;
9874   if (first.lastPing != first.lastPong) {
9875     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9876     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9877     return;
9878   }
9879   if (second.lastPing != second.lastPong) {
9880     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9881     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9882     return;
9883   }
9884   DisplayMessage("", ""); curMess = 0;
9885   ThawUI();
9886   TwoMachinesEvent();
9887 }
9888
9889 char *
9890 MakeName (char *template)
9891 {
9892     time_t clock;
9893     struct tm *tm;
9894     static char buf[MSG_SIZ];
9895     char *p = buf;
9896     int i;
9897
9898     clock = time((time_t *)NULL);
9899     tm = localtime(&clock);
9900
9901     while(*p++ = *template++) if(p[-1] == '%') {
9902         switch(*template++) {
9903           case 0:   *p = 0; return buf;
9904           case 'Y': i = tm->tm_year+1900; break;
9905           case 'y': i = tm->tm_year-100; break;
9906           case 'M': i = tm->tm_mon+1; break;
9907           case 'd': i = tm->tm_mday; break;
9908           case 'h': i = tm->tm_hour; break;
9909           case 'm': i = tm->tm_min; break;
9910           case 's': i = tm->tm_sec; break;
9911           default:  i = 0;
9912         }
9913         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9914     }
9915     return buf;
9916 }
9917
9918 int
9919 CountPlayers (char *p)
9920 {
9921     int n = 0;
9922     while(p = strchr(p, '\n')) p++, n++; // count participants
9923     return n;
9924 }
9925
9926 FILE *
9927 WriteTourneyFile (char *results, FILE *f)
9928 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9929     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9930     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9931         // create a file with tournament description
9932         fprintf(f, "-participants {%s}\n", appData.participants);
9933         fprintf(f, "-seedBase %d\n", appData.seedBase);
9934         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9935         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9936         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9937         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9938         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9939         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9940         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9941         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9942         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9943         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9944         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9945         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9946         if(searchTime > 0)
9947                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9948         else {
9949                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9950                 fprintf(f, "-tc %s\n", appData.timeControl);
9951                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9952         }
9953         fprintf(f, "-results \"%s\"\n", results);
9954     }
9955     return f;
9956 }
9957
9958 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9959
9960 void
9961 Substitute (char *participants, int expunge)
9962 {
9963     int i, changed, changes=0, nPlayers=0;
9964     char *p, *q, *r, buf[MSG_SIZ];
9965     if(participants == NULL) return;
9966     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9967     r = p = participants; q = appData.participants;
9968     while(*p && *p == *q) {
9969         if(*p == '\n') r = p+1, nPlayers++;
9970         p++; q++;
9971     }
9972     if(*p) { // difference
9973         while(*p && *p++ != '\n');
9974         while(*q && *q++ != '\n');
9975       changed = nPlayers;
9976         changes = 1 + (strcmp(p, q) != 0);
9977     }
9978     if(changes == 1) { // a single engine mnemonic was changed
9979         q = r; while(*q) nPlayers += (*q++ == '\n');
9980         p = buf; while(*r && (*p = *r++) != '\n') p++;
9981         *p = NULLCHAR;
9982         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9983         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9984         if(mnemonic[i]) { // The substitute is valid
9985             FILE *f;
9986             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9987                 flock(fileno(f), LOCK_EX);
9988                 ParseArgsFromFile(f);
9989                 fseek(f, 0, SEEK_SET);
9990                 FREE(appData.participants); appData.participants = participants;
9991                 if(expunge) { // erase results of replaced engine
9992                     int len = strlen(appData.results), w, b, dummy;
9993                     for(i=0; i<len; i++) {
9994                         Pairing(i, nPlayers, &w, &b, &dummy);
9995                         if((w == changed || b == changed) && appData.results[i] == '*') {
9996                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9997                             fclose(f);
9998                             return;
9999                         }
10000                     }
10001                     for(i=0; i<len; i++) {
10002                         Pairing(i, nPlayers, &w, &b, &dummy);
10003                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10004                     }
10005                 }
10006                 WriteTourneyFile(appData.results, f);
10007                 fclose(f); // release lock
10008                 return;
10009             }
10010         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10011     }
10012     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10013     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10014     free(participants);
10015     return;
10016 }
10017
10018 int
10019 CreateTourney (char *name)
10020 {
10021         FILE *f;
10022         if(matchMode && strcmp(name, appData.tourneyFile)) {
10023              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10024         }
10025         if(name[0] == NULLCHAR) {
10026             if(appData.participants[0])
10027                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10028             return 0;
10029         }
10030         f = fopen(name, "r");
10031         if(f) { // file exists
10032             ASSIGN(appData.tourneyFile, name);
10033             ParseArgsFromFile(f); // parse it
10034         } else {
10035             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10036             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10037                 DisplayError(_("Not enough participants"), 0);
10038                 return 0;
10039             }
10040             ASSIGN(appData.tourneyFile, name);
10041             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10042             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10043         }
10044         fclose(f);
10045         appData.noChessProgram = FALSE;
10046         appData.clockMode = TRUE;
10047         SetGNUMode();
10048         return 1;
10049 }
10050
10051 int
10052 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10053 {
10054     char buf[MSG_SIZ], *p, *q;
10055     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10056     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10057     skip = !all && group[0]; // if group requested, we start in skip mode
10058     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10059         p = names; q = buf; header = 0;
10060         while(*p && *p != '\n') *q++ = *p++;
10061         *q = 0;
10062         if(*p == '\n') p++;
10063         if(buf[0] == '#') {
10064             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10065             depth++; // we must be entering a new group
10066             if(all) continue; // suppress printing group headers when complete list requested
10067             header = 1;
10068             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10069         }
10070         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10071         if(engineList[i]) free(engineList[i]);
10072         engineList[i] = strdup(buf);
10073         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10074         if(engineMnemonic[i]) free(engineMnemonic[i]);
10075         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10076             strcat(buf, " (");
10077             sscanf(q + 8, "%s", buf + strlen(buf));
10078             strcat(buf, ")");
10079         }
10080         engineMnemonic[i] = strdup(buf);
10081         i++;
10082     }
10083     engineList[i] = engineMnemonic[i] = NULL;
10084     return i;
10085 }
10086
10087 // following implemented as macro to avoid type limitations
10088 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10089
10090 void
10091 SwapEngines (int n)
10092 {   // swap settings for first engine and other engine (so far only some selected options)
10093     int h;
10094     char *p;
10095     if(n == 0) return;
10096     SWAP(directory, p)
10097     SWAP(chessProgram, p)
10098     SWAP(isUCI, h)
10099     SWAP(hasOwnBookUCI, h)
10100     SWAP(protocolVersion, h)
10101     SWAP(reuse, h)
10102     SWAP(scoreIsAbsolute, h)
10103     SWAP(timeOdds, h)
10104     SWAP(logo, p)
10105     SWAP(pgnName, p)
10106     SWAP(pvSAN, h)
10107     SWAP(engOptions, p)
10108     SWAP(engInitString, p)
10109     SWAP(computerString, p)
10110     SWAP(features, p)
10111     SWAP(fenOverride, p)
10112     SWAP(NPS, h)
10113     SWAP(accumulateTC, h)
10114     SWAP(host, p)
10115 }
10116
10117 int
10118 SetPlayer (int player, char *p)
10119 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10120     int i;
10121     char buf[MSG_SIZ], *engineName;
10122     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10123     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10124     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10125     if(mnemonic[i]) {
10126         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10127         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10128         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10129         ParseArgsFromString(buf);
10130     }
10131     free(engineName);
10132     return i;
10133 }
10134
10135 char *recentEngines;
10136
10137 void
10138 RecentEngineEvent (int nr)
10139 {
10140     int n;
10141 //    SwapEngines(1); // bump first to second
10142 //    ReplaceEngine(&second, 1); // and load it there
10143     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10144     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10145     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10146         ReplaceEngine(&first, 0);
10147         FloatToFront(&appData.recentEngineList, command[n]);
10148     }
10149 }
10150
10151 int
10152 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10153 {   // determine players from game number
10154     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10155
10156     if(appData.tourneyType == 0) {
10157         roundsPerCycle = (nPlayers - 1) | 1;
10158         pairingsPerRound = nPlayers / 2;
10159     } else if(appData.tourneyType > 0) {
10160         roundsPerCycle = nPlayers - appData.tourneyType;
10161         pairingsPerRound = appData.tourneyType;
10162     }
10163     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10164     gamesPerCycle = gamesPerRound * roundsPerCycle;
10165     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10166     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10167     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10168     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10169     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10170     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10171
10172     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10173     if(appData.roundSync) *syncInterval = gamesPerRound;
10174
10175     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10176
10177     if(appData.tourneyType == 0) {
10178         if(curPairing == (nPlayers-1)/2 ) {
10179             *whitePlayer = curRound;
10180             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10181         } else {
10182             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10183             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10184             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10185             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10186         }
10187     } else if(appData.tourneyType > 1) {
10188         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10189         *whitePlayer = curRound + appData.tourneyType;
10190     } else if(appData.tourneyType > 0) {
10191         *whitePlayer = curPairing;
10192         *blackPlayer = curRound + appData.tourneyType;
10193     }
10194
10195     // take care of white/black alternation per round. 
10196     // For cycles and games this is already taken care of by default, derived from matchGame!
10197     return curRound & 1;
10198 }
10199
10200 int
10201 NextTourneyGame (int nr, int *swapColors)
10202 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10203     char *p, *q;
10204     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10205     FILE *tf;
10206     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10207     tf = fopen(appData.tourneyFile, "r");
10208     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10209     ParseArgsFromFile(tf); fclose(tf);
10210     InitTimeControls(); // TC might be altered from tourney file
10211
10212     nPlayers = CountPlayers(appData.participants); // count participants
10213     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10214     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10215
10216     if(syncInterval) {
10217         p = q = appData.results;
10218         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10219         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10220             DisplayMessage(_("Waiting for other game(s)"),"");
10221             waitingForGame = TRUE;
10222             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10223             return 0;
10224         }
10225         waitingForGame = FALSE;
10226     }
10227
10228     if(appData.tourneyType < 0) {
10229         if(nr>=0 && !pairingReceived) {
10230             char buf[1<<16];
10231             if(pairing.pr == NoProc) {
10232                 if(!appData.pairingEngine[0]) {
10233                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10234                     return 0;
10235                 }
10236                 StartChessProgram(&pairing); // starts the pairing engine
10237             }
10238             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10239             SendToProgram(buf, &pairing);
10240             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10241             SendToProgram(buf, &pairing);
10242             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10243         }
10244         pairingReceived = 0;                              // ... so we continue here 
10245         *swapColors = 0;
10246         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10247         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10248         matchGame = 1; roundNr = nr / syncInterval + 1;
10249     }
10250
10251     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10252
10253     // redefine engines, engine dir, etc.
10254     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10255     if(first.pr == NoProc) {
10256       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10257       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10258     }
10259     if(second.pr == NoProc) {
10260       SwapEngines(1);
10261       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10262       SwapEngines(1);         // and make that valid for second engine by swapping
10263       InitEngine(&second, 1);
10264     }
10265     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10266     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10267     return 1;
10268 }
10269
10270 void
10271 NextMatchGame ()
10272 {   // performs game initialization that does not invoke engines, and then tries to start the game
10273     int res, firstWhite, swapColors = 0;
10274     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10275     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
10276         char buf[MSG_SIZ];
10277         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10278         if(strcmp(buf, currentDebugFile)) { // name has changed
10279             FILE *f = fopen(buf, "w");
10280             if(f) { // if opening the new file failed, just keep using the old one
10281                 ASSIGN(currentDebugFile, buf);
10282                 fclose(debugFP);
10283                 debugFP = f;
10284             }
10285             if(appData.serverFileName) {
10286                 if(serverFP) fclose(serverFP);
10287                 serverFP = fopen(appData.serverFileName, "w");
10288                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10289                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10290             }
10291         }
10292     }
10293     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10294     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10295     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10296     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10297     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10298     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10299     Reset(FALSE, first.pr != NoProc);
10300     res = LoadGameOrPosition(matchGame); // setup game
10301     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10302     if(!res) return; // abort when bad game/pos file
10303     TwoMachinesEvent();
10304 }
10305
10306 void
10307 UserAdjudicationEvent (int result)
10308 {
10309     ChessMove gameResult = GameIsDrawn;
10310
10311     if( result > 0 ) {
10312         gameResult = WhiteWins;
10313     }
10314     else if( result < 0 ) {
10315         gameResult = BlackWins;
10316     }
10317
10318     if( gameMode == TwoMachinesPlay ) {
10319         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10320     }
10321 }
10322
10323
10324 // [HGM] save: calculate checksum of game to make games easily identifiable
10325 int
10326 StringCheckSum (char *s)
10327 {
10328         int i = 0;
10329         if(s==NULL) return 0;
10330         while(*s) i = i*259 + *s++;
10331         return i;
10332 }
10333
10334 int
10335 GameCheckSum ()
10336 {
10337         int i, sum=0;
10338         for(i=backwardMostMove; i<forwardMostMove; i++) {
10339                 sum += pvInfoList[i].depth;
10340                 sum += StringCheckSum(parseList[i]);
10341                 sum += StringCheckSum(commentList[i]);
10342                 sum *= 261;
10343         }
10344         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10345         return sum + StringCheckSum(commentList[i]);
10346 } // end of save patch
10347
10348 void
10349 GameEnds (ChessMove result, char *resultDetails, int whosays)
10350 {
10351     GameMode nextGameMode;
10352     int isIcsGame;
10353     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10354
10355     if(endingGame) return; /* [HGM] crash: forbid recursion */
10356     endingGame = 1;
10357     if(twoBoards) { // [HGM] dual: switch back to one board
10358         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10359         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10360     }
10361     if (appData.debugMode) {
10362       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10363               result, resultDetails ? resultDetails : "(null)", whosays);
10364     }
10365
10366     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10367
10368     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10369         /* If we are playing on ICS, the server decides when the
10370            game is over, but the engine can offer to draw, claim
10371            a draw, or resign.
10372          */
10373 #if ZIPPY
10374         if (appData.zippyPlay && first.initDone) {
10375             if (result == GameIsDrawn) {
10376                 /* In case draw still needs to be claimed */
10377                 SendToICS(ics_prefix);
10378                 SendToICS("draw\n");
10379             } else if (StrCaseStr(resultDetails, "resign")) {
10380                 SendToICS(ics_prefix);
10381                 SendToICS("resign\n");
10382             }
10383         }
10384 #endif
10385         endingGame = 0; /* [HGM] crash */
10386         return;
10387     }
10388
10389     /* If we're loading the game from a file, stop */
10390     if (whosays == GE_FILE) {
10391       (void) StopLoadGameTimer();
10392       gameFileFP = NULL;
10393     }
10394
10395     /* Cancel draw offers */
10396     first.offeredDraw = second.offeredDraw = 0;
10397
10398     /* If this is an ICS game, only ICS can really say it's done;
10399        if not, anyone can. */
10400     isIcsGame = (gameMode == IcsPlayingWhite ||
10401                  gameMode == IcsPlayingBlack ||
10402                  gameMode == IcsObserving    ||
10403                  gameMode == IcsExamining);
10404
10405     if (!isIcsGame || whosays == GE_ICS) {
10406         /* OK -- not an ICS game, or ICS said it was done */
10407         StopClocks();
10408         if (!isIcsGame && !appData.noChessProgram)
10409           SetUserThinkingEnables();
10410
10411         /* [HGM] if a machine claims the game end we verify this claim */
10412         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10413             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10414                 char claimer;
10415                 ChessMove trueResult = (ChessMove) -1;
10416
10417                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10418                                             first.twoMachinesColor[0] :
10419                                             second.twoMachinesColor[0] ;
10420
10421                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10422                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10423                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10424                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10425                 } else
10426                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10427                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10428                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10429                 } else
10430                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10431                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10432                 }
10433
10434                 // now verify win claims, but not in drop games, as we don't understand those yet
10435                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10436                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10437                     (result == WhiteWins && claimer == 'w' ||
10438                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10439                       if (appData.debugMode) {
10440                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10441                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10442                       }
10443                       if(result != trueResult) {
10444                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10445                               result = claimer == 'w' ? BlackWins : WhiteWins;
10446                               resultDetails = buf;
10447                       }
10448                 } else
10449                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10450                     && (forwardMostMove <= backwardMostMove ||
10451                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10452                         (claimer=='b')==(forwardMostMove&1))
10453                                                                                   ) {
10454                       /* [HGM] verify: draws that were not flagged are false claims */
10455                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10456                       result = claimer == 'w' ? BlackWins : WhiteWins;
10457                       resultDetails = buf;
10458                 }
10459                 /* (Claiming a loss is accepted no questions asked!) */
10460             }
10461             /* [HGM] bare: don't allow bare King to win */
10462             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10463                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10464                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10465                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10466                && result != GameIsDrawn)
10467             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10468                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10469                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10470                         if(p >= 0 && p <= (int)WhiteKing) k++;
10471                 }
10472                 if (appData.debugMode) {
10473                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10474                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10475                 }
10476                 if(k <= 1) {
10477                         result = GameIsDrawn;
10478                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10479                         resultDetails = buf;
10480                 }
10481             }
10482         }
10483
10484
10485         if(serverMoves != NULL && !loadFlag) { char c = '=';
10486             if(result==WhiteWins) c = '+';
10487             if(result==BlackWins) c = '-';
10488             if(resultDetails != NULL)
10489                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10490         }
10491         if (resultDetails != NULL) {
10492             gameInfo.result = result;
10493             gameInfo.resultDetails = StrSave(resultDetails);
10494
10495             /* display last move only if game was not loaded from file */
10496             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10497                 DisplayMove(currentMove - 1);
10498
10499             if (forwardMostMove != 0) {
10500                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10501                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10502                                                                 ) {
10503                     if (*appData.saveGameFile != NULLCHAR) {
10504                         SaveGameToFile(appData.saveGameFile, TRUE);
10505                     } else if (appData.autoSaveGames) {
10506                         AutoSaveGame();
10507                     }
10508                     if (*appData.savePositionFile != NULLCHAR) {
10509                         SavePositionToFile(appData.savePositionFile);
10510                     }
10511                 }
10512             }
10513
10514             /* Tell program how game ended in case it is learning */
10515             /* [HGM] Moved this to after saving the PGN, just in case */
10516             /* engine died and we got here through time loss. In that */
10517             /* case we will get a fatal error writing the pipe, which */
10518             /* would otherwise lose us the PGN.                       */
10519             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10520             /* output during GameEnds should never be fatal anymore   */
10521             if (gameMode == MachinePlaysWhite ||
10522                 gameMode == MachinePlaysBlack ||
10523                 gameMode == TwoMachinesPlay ||
10524                 gameMode == IcsPlayingWhite ||
10525                 gameMode == IcsPlayingBlack ||
10526                 gameMode == BeginningOfGame) {
10527                 char buf[MSG_SIZ];
10528                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10529                         resultDetails);
10530                 if (first.pr != NoProc) {
10531                     SendToProgram(buf, &first);
10532                 }
10533                 if (second.pr != NoProc &&
10534                     gameMode == TwoMachinesPlay) {
10535                     SendToProgram(buf, &second);
10536                 }
10537             }
10538         }
10539
10540         if (appData.icsActive) {
10541             if (appData.quietPlay &&
10542                 (gameMode == IcsPlayingWhite ||
10543                  gameMode == IcsPlayingBlack)) {
10544                 SendToICS(ics_prefix);
10545                 SendToICS("set shout 1\n");
10546             }
10547             nextGameMode = IcsIdle;
10548             ics_user_moved = FALSE;
10549             /* clean up premove.  It's ugly when the game has ended and the
10550              * premove highlights are still on the board.
10551              */
10552             if (gotPremove) {
10553               gotPremove = FALSE;
10554               ClearPremoveHighlights();
10555               DrawPosition(FALSE, boards[currentMove]);
10556             }
10557             if (whosays == GE_ICS) {
10558                 switch (result) {
10559                 case WhiteWins:
10560                     if (gameMode == IcsPlayingWhite)
10561                         PlayIcsWinSound();
10562                     else if(gameMode == IcsPlayingBlack)
10563                         PlayIcsLossSound();
10564                     break;
10565                 case BlackWins:
10566                     if (gameMode == IcsPlayingBlack)
10567                         PlayIcsWinSound();
10568                     else if(gameMode == IcsPlayingWhite)
10569                         PlayIcsLossSound();
10570                     break;
10571                 case GameIsDrawn:
10572                     PlayIcsDrawSound();
10573                     break;
10574                 default:
10575                     PlayIcsUnfinishedSound();
10576                 }
10577             }
10578         } else if (gameMode == EditGame ||
10579                    gameMode == PlayFromGameFile ||
10580                    gameMode == AnalyzeMode ||
10581                    gameMode == AnalyzeFile) {
10582             nextGameMode = gameMode;
10583         } else {
10584             nextGameMode = EndOfGame;
10585         }
10586         pausing = FALSE;
10587         ModeHighlight();
10588     } else {
10589         nextGameMode = gameMode;
10590     }
10591
10592     if (appData.noChessProgram) {
10593         gameMode = nextGameMode;
10594         ModeHighlight();
10595         endingGame = 0; /* [HGM] crash */
10596         return;
10597     }
10598
10599     if (first.reuse) {
10600         /* Put first chess program into idle state */
10601         if (first.pr != NoProc &&
10602             (gameMode == MachinePlaysWhite ||
10603              gameMode == MachinePlaysBlack ||
10604              gameMode == TwoMachinesPlay ||
10605              gameMode == IcsPlayingWhite ||
10606              gameMode == IcsPlayingBlack ||
10607              gameMode == BeginningOfGame)) {
10608             SendToProgram("force\n", &first);
10609             if (first.usePing) {
10610               char buf[MSG_SIZ];
10611               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10612               SendToProgram(buf, &first);
10613             }
10614         }
10615     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10616         /* Kill off first chess program */
10617         if (first.isr != NULL)
10618           RemoveInputSource(first.isr);
10619         first.isr = NULL;
10620
10621         if (first.pr != NoProc) {
10622             ExitAnalyzeMode();
10623             DoSleep( appData.delayBeforeQuit );
10624             SendToProgram("quit\n", &first);
10625             DoSleep( appData.delayAfterQuit );
10626             DestroyChildProcess(first.pr, first.useSigterm);
10627         }
10628         first.pr = NoProc;
10629     }
10630     if (second.reuse) {
10631         /* Put second chess program into idle state */
10632         if (second.pr != NoProc &&
10633             gameMode == TwoMachinesPlay) {
10634             SendToProgram("force\n", &second);
10635             if (second.usePing) {
10636               char buf[MSG_SIZ];
10637               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10638               SendToProgram(buf, &second);
10639             }
10640         }
10641     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10642         /* Kill off second chess program */
10643         if (second.isr != NULL)
10644           RemoveInputSource(second.isr);
10645         second.isr = NULL;
10646
10647         if (second.pr != NoProc) {
10648             DoSleep( appData.delayBeforeQuit );
10649             SendToProgram("quit\n", &second);
10650             DoSleep( appData.delayAfterQuit );
10651             DestroyChildProcess(second.pr, second.useSigterm);
10652         }
10653         second.pr = NoProc;
10654     }
10655
10656     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10657         char resChar = '=';
10658         switch (result) {
10659         case WhiteWins:
10660           resChar = '+';
10661           if (first.twoMachinesColor[0] == 'w') {
10662             first.matchWins++;
10663           } else {
10664             second.matchWins++;
10665           }
10666           break;
10667         case BlackWins:
10668           resChar = '-';
10669           if (first.twoMachinesColor[0] == 'b') {
10670             first.matchWins++;
10671           } else {
10672             second.matchWins++;
10673           }
10674           break;
10675         case GameUnfinished:
10676           resChar = ' ';
10677         default:
10678           break;
10679         }
10680
10681         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10682         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10683             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10684             ReserveGame(nextGame, resChar); // sets nextGame
10685             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10686             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10687         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10688
10689         if (nextGame <= appData.matchGames && !abortMatch) {
10690             gameMode = nextGameMode;
10691             matchGame = nextGame; // this will be overruled in tourney mode!
10692             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10693             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10694             endingGame = 0; /* [HGM] crash */
10695             return;
10696         } else {
10697             gameMode = nextGameMode;
10698             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10699                      first.tidy, second.tidy,
10700                      first.matchWins, second.matchWins,
10701                      appData.matchGames - (first.matchWins + second.matchWins));
10702             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10703             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10704             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10705             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10706                 first.twoMachinesColor = "black\n";
10707                 second.twoMachinesColor = "white\n";
10708             } else {
10709                 first.twoMachinesColor = "white\n";
10710                 second.twoMachinesColor = "black\n";
10711             }
10712         }
10713     }
10714     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10715         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10716       ExitAnalyzeMode();
10717     gameMode = nextGameMode;
10718     ModeHighlight();
10719     endingGame = 0;  /* [HGM] crash */
10720     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10721         if(matchMode == TRUE) { // match through command line: exit with or without popup
10722             if(ranking) {
10723                 ToNrEvent(forwardMostMove);
10724                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10725                 else ExitEvent(0);
10726             } else DisplayFatalError(buf, 0, 0);
10727         } else { // match through menu; just stop, with or without popup
10728             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10729             ModeHighlight();
10730             if(ranking){
10731                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10732             } else DisplayNote(buf);
10733       }
10734       if(ranking) free(ranking);
10735     }
10736 }
10737
10738 /* Assumes program was just initialized (initString sent).
10739    Leaves program in force mode. */
10740 void
10741 FeedMovesToProgram (ChessProgramState *cps, int upto)
10742 {
10743     int i;
10744
10745     if (appData.debugMode)
10746       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10747               startedFromSetupPosition ? "position and " : "",
10748               backwardMostMove, upto, cps->which);
10749     if(currentlyInitializedVariant != gameInfo.variant) {
10750       char buf[MSG_SIZ];
10751         // [HGM] variantswitch: make engine aware of new variant
10752         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10753                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10754         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10755         SendToProgram(buf, cps);
10756         currentlyInitializedVariant = gameInfo.variant;
10757     }
10758     SendToProgram("force\n", cps);
10759     if (startedFromSetupPosition) {
10760         SendBoard(cps, backwardMostMove);
10761     if (appData.debugMode) {
10762         fprintf(debugFP, "feedMoves\n");
10763     }
10764     }
10765     for (i = backwardMostMove; i < upto; i++) {
10766         SendMoveToProgram(i, cps);
10767     }
10768 }
10769
10770
10771 int
10772 ResurrectChessProgram ()
10773 {
10774      /* The chess program may have exited.
10775         If so, restart it and feed it all the moves made so far. */
10776     static int doInit = 0;
10777
10778     if (appData.noChessProgram) return 1;
10779
10780     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10781         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10782         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10783         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10784     } else {
10785         if (first.pr != NoProc) return 1;
10786         StartChessProgram(&first);
10787     }
10788     InitChessProgram(&first, FALSE);
10789     FeedMovesToProgram(&first, currentMove);
10790
10791     if (!first.sendTime) {
10792         /* can't tell gnuchess what its clock should read,
10793            so we bow to its notion. */
10794         ResetClocks();
10795         timeRemaining[0][currentMove] = whiteTimeRemaining;
10796         timeRemaining[1][currentMove] = blackTimeRemaining;
10797     }
10798
10799     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10800                 appData.icsEngineAnalyze) && first.analysisSupport) {
10801       SendToProgram("analyze\n", &first);
10802       first.analyzing = TRUE;
10803     }
10804     return 1;
10805 }
10806
10807 /*
10808  * Button procedures
10809  */
10810 void
10811 Reset (int redraw, int init)
10812 {
10813     int i;
10814
10815     if (appData.debugMode) {
10816         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10817                 redraw, init, gameMode);
10818     }
10819     CleanupTail(); // [HGM] vari: delete any stored variations
10820     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10821     pausing = pauseExamInvalid = FALSE;
10822     startedFromSetupPosition = blackPlaysFirst = FALSE;
10823     firstMove = TRUE;
10824     whiteFlag = blackFlag = FALSE;
10825     userOfferedDraw = FALSE;
10826     hintRequested = bookRequested = FALSE;
10827     first.maybeThinking = FALSE;
10828     second.maybeThinking = FALSE;
10829     first.bookSuspend = FALSE; // [HGM] book
10830     second.bookSuspend = FALSE;
10831     thinkOutput[0] = NULLCHAR;
10832     lastHint[0] = NULLCHAR;
10833     ClearGameInfo(&gameInfo);
10834     gameInfo.variant = StringToVariant(appData.variant);
10835     ics_user_moved = ics_clock_paused = FALSE;
10836     ics_getting_history = H_FALSE;
10837     ics_gamenum = -1;
10838     white_holding[0] = black_holding[0] = NULLCHAR;
10839     ClearProgramStats();
10840     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10841
10842     ResetFrontEnd();
10843     ClearHighlights();
10844     flipView = appData.flipView;
10845     ClearPremoveHighlights();
10846     gotPremove = FALSE;
10847     alarmSounded = FALSE;
10848
10849     GameEnds(EndOfFile, NULL, GE_PLAYER);
10850     if(appData.serverMovesName != NULL) {
10851         /* [HGM] prepare to make moves file for broadcasting */
10852         clock_t t = clock();
10853         if(serverMoves != NULL) fclose(serverMoves);
10854         serverMoves = fopen(appData.serverMovesName, "r");
10855         if(serverMoves != NULL) {
10856             fclose(serverMoves);
10857             /* delay 15 sec before overwriting, so all clients can see end */
10858             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10859         }
10860         serverMoves = fopen(appData.serverMovesName, "w");
10861     }
10862
10863     ExitAnalyzeMode();
10864     gameMode = BeginningOfGame;
10865     ModeHighlight();
10866     if(appData.icsActive) gameInfo.variant = VariantNormal;
10867     currentMove = forwardMostMove = backwardMostMove = 0;
10868     MarkTargetSquares(1);
10869     InitPosition(redraw);
10870     for (i = 0; i < MAX_MOVES; i++) {
10871         if (commentList[i] != NULL) {
10872             free(commentList[i]);
10873             commentList[i] = NULL;
10874         }
10875     }
10876     ResetClocks();
10877     timeRemaining[0][0] = whiteTimeRemaining;
10878     timeRemaining[1][0] = blackTimeRemaining;
10879
10880     if (first.pr == NoProc) {
10881         StartChessProgram(&first);
10882     }
10883     if (init) {
10884             InitChessProgram(&first, startedFromSetupPosition);
10885     }
10886     DisplayTitle("");
10887     DisplayMessage("", "");
10888     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10889     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10890     ClearMap();        // [HGM] exclude: invalidate map
10891 }
10892
10893 void
10894 AutoPlayGameLoop ()
10895 {
10896     for (;;) {
10897         if (!AutoPlayOneMove())
10898           return;
10899         if (matchMode || appData.timeDelay == 0)
10900           continue;
10901         if (appData.timeDelay < 0)
10902           return;
10903         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10904         break;
10905     }
10906 }
10907
10908
10909 int
10910 AutoPlayOneMove ()
10911 {
10912     int fromX, fromY, toX, toY;
10913
10914     if (appData.debugMode) {
10915       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10916     }
10917
10918     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10919       return FALSE;
10920
10921     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10922       pvInfoList[currentMove].depth = programStats.depth;
10923       pvInfoList[currentMove].score = programStats.score;
10924       pvInfoList[currentMove].time  = 0;
10925       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10926     }
10927
10928     if (currentMove >= forwardMostMove) {
10929       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10930 //      gameMode = EndOfGame;
10931 //      ModeHighlight();
10932
10933       /* [AS] Clear current move marker at the end of a game */
10934       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10935
10936       return FALSE;
10937     }
10938
10939     toX = moveList[currentMove][2] - AAA;
10940     toY = moveList[currentMove][3] - ONE;
10941
10942     if (moveList[currentMove][1] == '@') {
10943         if (appData.highlightLastMove) {
10944             SetHighlights(-1, -1, toX, toY);
10945         }
10946     } else {
10947         fromX = moveList[currentMove][0] - AAA;
10948         fromY = moveList[currentMove][1] - ONE;
10949
10950         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10951
10952         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10953
10954         if (appData.highlightLastMove) {
10955             SetHighlights(fromX, fromY, toX, toY);
10956         }
10957     }
10958     DisplayMove(currentMove);
10959     SendMoveToProgram(currentMove++, &first);
10960     DisplayBothClocks();
10961     DrawPosition(FALSE, boards[currentMove]);
10962     // [HGM] PV info: always display, routine tests if empty
10963     DisplayComment(currentMove - 1, commentList[currentMove]);
10964     return TRUE;
10965 }
10966
10967
10968 int
10969 LoadGameOneMove (ChessMove readAhead)
10970 {
10971     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10972     char promoChar = NULLCHAR;
10973     ChessMove moveType;
10974     char move[MSG_SIZ];
10975     char *p, *q;
10976
10977     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10978         gameMode != AnalyzeMode && gameMode != Training) {
10979         gameFileFP = NULL;
10980         return FALSE;
10981     }
10982
10983     yyboardindex = forwardMostMove;
10984     if (readAhead != EndOfFile) {
10985       moveType = readAhead;
10986     } else {
10987       if (gameFileFP == NULL)
10988           return FALSE;
10989       moveType = (ChessMove) Myylex();
10990     }
10991
10992     done = FALSE;
10993     switch (moveType) {
10994       case Comment:
10995         if (appData.debugMode)
10996           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10997         p = yy_text;
10998
10999         /* append the comment but don't display it */
11000         AppendComment(currentMove, p, FALSE);
11001         return TRUE;
11002
11003       case WhiteCapturesEnPassant:
11004       case BlackCapturesEnPassant:
11005       case WhitePromotion:
11006       case BlackPromotion:
11007       case WhiteNonPromotion:
11008       case BlackNonPromotion:
11009       case NormalMove:
11010       case WhiteKingSideCastle:
11011       case WhiteQueenSideCastle:
11012       case BlackKingSideCastle:
11013       case BlackQueenSideCastle:
11014       case WhiteKingSideCastleWild:
11015       case WhiteQueenSideCastleWild:
11016       case BlackKingSideCastleWild:
11017       case BlackQueenSideCastleWild:
11018       /* PUSH Fabien */
11019       case WhiteHSideCastleFR:
11020       case WhiteASideCastleFR:
11021       case BlackHSideCastleFR:
11022       case BlackASideCastleFR:
11023       /* POP Fabien */
11024         if (appData.debugMode)
11025           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11026         fromX = currentMoveString[0] - AAA;
11027         fromY = currentMoveString[1] - ONE;
11028         toX = currentMoveString[2] - AAA;
11029         toY = currentMoveString[3] - ONE;
11030         promoChar = currentMoveString[4];
11031         break;
11032
11033       case WhiteDrop:
11034       case BlackDrop:
11035         if (appData.debugMode)
11036           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11037         fromX = moveType == WhiteDrop ?
11038           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11039         (int) CharToPiece(ToLower(currentMoveString[0]));
11040         fromY = DROP_RANK;
11041         toX = currentMoveString[2] - AAA;
11042         toY = currentMoveString[3] - ONE;
11043         break;
11044
11045       case WhiteWins:
11046       case BlackWins:
11047       case GameIsDrawn:
11048       case GameUnfinished:
11049         if (appData.debugMode)
11050           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11051         p = strchr(yy_text, '{');
11052         if (p == NULL) p = strchr(yy_text, '(');
11053         if (p == NULL) {
11054             p = yy_text;
11055             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11056         } else {
11057             q = strchr(p, *p == '{' ? '}' : ')');
11058             if (q != NULL) *q = NULLCHAR;
11059             p++;
11060         }
11061         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11062         GameEnds(moveType, p, GE_FILE);
11063         done = TRUE;
11064         if (cmailMsgLoaded) {
11065             ClearHighlights();
11066             flipView = WhiteOnMove(currentMove);
11067             if (moveType == GameUnfinished) flipView = !flipView;
11068             if (appData.debugMode)
11069               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11070         }
11071         break;
11072
11073       case EndOfFile:
11074         if (appData.debugMode)
11075           fprintf(debugFP, "Parser hit end of file\n");
11076         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11077           case MT_NONE:
11078           case MT_CHECK:
11079             break;
11080           case MT_CHECKMATE:
11081           case MT_STAINMATE:
11082             if (WhiteOnMove(currentMove)) {
11083                 GameEnds(BlackWins, "Black mates", GE_FILE);
11084             } else {
11085                 GameEnds(WhiteWins, "White mates", GE_FILE);
11086             }
11087             break;
11088           case MT_STALEMATE:
11089             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11090             break;
11091         }
11092         done = TRUE;
11093         break;
11094
11095       case MoveNumberOne:
11096         if (lastLoadGameStart == GNUChessGame) {
11097             /* GNUChessGames have numbers, but they aren't move numbers */
11098             if (appData.debugMode)
11099               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11100                       yy_text, (int) moveType);
11101             return LoadGameOneMove(EndOfFile); /* tail recursion */
11102         }
11103         /* else fall thru */
11104
11105       case XBoardGame:
11106       case GNUChessGame:
11107       case PGNTag:
11108         /* Reached start of next game in file */
11109         if (appData.debugMode)
11110           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11111         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11112           case MT_NONE:
11113           case MT_CHECK:
11114             break;
11115           case MT_CHECKMATE:
11116           case MT_STAINMATE:
11117             if (WhiteOnMove(currentMove)) {
11118                 GameEnds(BlackWins, "Black mates", GE_FILE);
11119             } else {
11120                 GameEnds(WhiteWins, "White mates", GE_FILE);
11121             }
11122             break;
11123           case MT_STALEMATE:
11124             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11125             break;
11126         }
11127         done = TRUE;
11128         break;
11129
11130       case PositionDiagram:     /* should not happen; ignore */
11131       case ElapsedTime:         /* ignore */
11132       case NAG:                 /* ignore */
11133         if (appData.debugMode)
11134           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11135                   yy_text, (int) moveType);
11136         return LoadGameOneMove(EndOfFile); /* tail recursion */
11137
11138       case IllegalMove:
11139         if (appData.testLegality) {
11140             if (appData.debugMode)
11141               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11142             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11143                     (forwardMostMove / 2) + 1,
11144                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11145             DisplayError(move, 0);
11146             done = TRUE;
11147         } else {
11148             if (appData.debugMode)
11149               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11150                       yy_text, currentMoveString);
11151             fromX = currentMoveString[0] - AAA;
11152             fromY = currentMoveString[1] - ONE;
11153             toX = currentMoveString[2] - AAA;
11154             toY = currentMoveString[3] - ONE;
11155             promoChar = currentMoveString[4];
11156         }
11157         break;
11158
11159       case AmbiguousMove:
11160         if (appData.debugMode)
11161           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11162         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11163                 (forwardMostMove / 2) + 1,
11164                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11165         DisplayError(move, 0);
11166         done = TRUE;
11167         break;
11168
11169       default:
11170       case ImpossibleMove:
11171         if (appData.debugMode)
11172           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11173         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11174                 (forwardMostMove / 2) + 1,
11175                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11176         DisplayError(move, 0);
11177         done = TRUE;
11178         break;
11179     }
11180
11181     if (done) {
11182         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11183             DrawPosition(FALSE, boards[currentMove]);
11184             DisplayBothClocks();
11185             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11186               DisplayComment(currentMove - 1, commentList[currentMove]);
11187         }
11188         (void) StopLoadGameTimer();
11189         gameFileFP = NULL;
11190         cmailOldMove = forwardMostMove;
11191         return FALSE;
11192     } else {
11193         /* currentMoveString is set as a side-effect of yylex */
11194
11195         thinkOutput[0] = NULLCHAR;
11196         MakeMove(fromX, fromY, toX, toY, promoChar);
11197         currentMove = forwardMostMove;
11198         return TRUE;
11199     }
11200 }
11201
11202 /* Load the nth game from the given file */
11203 int
11204 LoadGameFromFile (char *filename, int n, char *title, int useList)
11205 {
11206     FILE *f;
11207     char buf[MSG_SIZ];
11208
11209     if (strcmp(filename, "-") == 0) {
11210         f = stdin;
11211         title = "stdin";
11212     } else {
11213         f = fopen(filename, "rb");
11214         if (f == NULL) {
11215           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11216             DisplayError(buf, errno);
11217             return FALSE;
11218         }
11219     }
11220     if (fseek(f, 0, 0) == -1) {
11221         /* f is not seekable; probably a pipe */
11222         useList = FALSE;
11223     }
11224     if (useList && n == 0) {
11225         int error = GameListBuild(f);
11226         if (error) {
11227             DisplayError(_("Cannot build game list"), error);
11228         } else if (!ListEmpty(&gameList) &&
11229                    ((ListGame *) gameList.tailPred)->number > 1) {
11230             GameListPopUp(f, title);
11231             return TRUE;
11232         }
11233         GameListDestroy();
11234         n = 1;
11235     }
11236     if (n == 0) n = 1;
11237     return LoadGame(f, n, title, FALSE);
11238 }
11239
11240
11241 void
11242 MakeRegisteredMove ()
11243 {
11244     int fromX, fromY, toX, toY;
11245     char promoChar;
11246     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11247         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11248           case CMAIL_MOVE:
11249           case CMAIL_DRAW:
11250             if (appData.debugMode)
11251               fprintf(debugFP, "Restoring %s for game %d\n",
11252                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11253
11254             thinkOutput[0] = NULLCHAR;
11255             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11256             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11257             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11258             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11259             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11260             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11261             MakeMove(fromX, fromY, toX, toY, promoChar);
11262             ShowMove(fromX, fromY, toX, toY);
11263
11264             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11265               case MT_NONE:
11266               case MT_CHECK:
11267                 break;
11268
11269               case MT_CHECKMATE:
11270               case MT_STAINMATE:
11271                 if (WhiteOnMove(currentMove)) {
11272                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11273                 } else {
11274                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11275                 }
11276                 break;
11277
11278               case MT_STALEMATE:
11279                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11280                 break;
11281             }
11282
11283             break;
11284
11285           case CMAIL_RESIGN:
11286             if (WhiteOnMove(currentMove)) {
11287                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11288             } else {
11289                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11290             }
11291             break;
11292
11293           case CMAIL_ACCEPT:
11294             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11295             break;
11296
11297           default:
11298             break;
11299         }
11300     }
11301
11302     return;
11303 }
11304
11305 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11306 int
11307 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11308 {
11309     int retVal;
11310
11311     if (gameNumber > nCmailGames) {
11312         DisplayError(_("No more games in this message"), 0);
11313         return FALSE;
11314     }
11315     if (f == lastLoadGameFP) {
11316         int offset = gameNumber - lastLoadGameNumber;
11317         if (offset == 0) {
11318             cmailMsg[0] = NULLCHAR;
11319             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11320                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11321                 nCmailMovesRegistered--;
11322             }
11323             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11324             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11325                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11326             }
11327         } else {
11328             if (! RegisterMove()) return FALSE;
11329         }
11330     }
11331
11332     retVal = LoadGame(f, gameNumber, title, useList);
11333
11334     /* Make move registered during previous look at this game, if any */
11335     MakeRegisteredMove();
11336
11337     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11338         commentList[currentMove]
11339           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11340         DisplayComment(currentMove - 1, commentList[currentMove]);
11341     }
11342
11343     return retVal;
11344 }
11345
11346 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11347 int
11348 ReloadGame (int offset)
11349 {
11350     int gameNumber = lastLoadGameNumber + offset;
11351     if (lastLoadGameFP == NULL) {
11352         DisplayError(_("No game has been loaded yet"), 0);
11353         return FALSE;
11354     }
11355     if (gameNumber <= 0) {
11356         DisplayError(_("Can't back up any further"), 0);
11357         return FALSE;
11358     }
11359     if (cmailMsgLoaded) {
11360         return CmailLoadGame(lastLoadGameFP, gameNumber,
11361                              lastLoadGameTitle, lastLoadGameUseList);
11362     } else {
11363         return LoadGame(lastLoadGameFP, gameNumber,
11364                         lastLoadGameTitle, lastLoadGameUseList);
11365     }
11366 }
11367
11368 int keys[EmptySquare+1];
11369
11370 int
11371 PositionMatches (Board b1, Board b2)
11372 {
11373     int r, f, sum=0;
11374     switch(appData.searchMode) {
11375         case 1: return CompareWithRights(b1, b2);
11376         case 2:
11377             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11378                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11379             }
11380             return TRUE;
11381         case 3:
11382             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11383               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11384                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11385             }
11386             return sum==0;
11387         case 4:
11388             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11389                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11390             }
11391             return sum==0;
11392     }
11393     return TRUE;
11394 }
11395
11396 #define Q_PROMO  4
11397 #define Q_EP     3
11398 #define Q_BCASTL 2
11399 #define Q_WCASTL 1
11400
11401 int pieceList[256], quickBoard[256];
11402 ChessSquare pieceType[256] = { EmptySquare };
11403 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11404 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11405 int soughtTotal, turn;
11406 Boolean epOK, flipSearch;
11407
11408 typedef struct {
11409     unsigned char piece, to;
11410 } Move;
11411
11412 #define DSIZE (250000)
11413
11414 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11415 Move *moveDatabase = initialSpace;
11416 unsigned int movePtr, dataSize = DSIZE;
11417
11418 int
11419 MakePieceList (Board board, int *counts)
11420 {
11421     int r, f, n=Q_PROMO, total=0;
11422     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11423     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11424         int sq = f + (r<<4);
11425         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11426             quickBoard[sq] = ++n;
11427             pieceList[n] = sq;
11428             pieceType[n] = board[r][f];
11429             counts[board[r][f]]++;
11430             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11431             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11432             total++;
11433         }
11434     }
11435     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11436     return total;
11437 }
11438
11439 void
11440 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11441 {
11442     int sq = fromX + (fromY<<4);
11443     int piece = quickBoard[sq];
11444     quickBoard[sq] = 0;
11445     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11446     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11447         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11448         moveDatabase[movePtr++].piece = Q_WCASTL;
11449         quickBoard[sq] = piece;
11450         piece = quickBoard[from]; quickBoard[from] = 0;
11451         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11452     } else
11453     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11454         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11455         moveDatabase[movePtr++].piece = Q_BCASTL;
11456         quickBoard[sq] = piece;
11457         piece = quickBoard[from]; quickBoard[from] = 0;
11458         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11459     } else
11460     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11461         quickBoard[(fromY<<4)+toX] = 0;
11462         moveDatabase[movePtr].piece = Q_EP;
11463         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11464         moveDatabase[movePtr].to = sq;
11465     } else
11466     if(promoPiece != pieceType[piece]) {
11467         moveDatabase[movePtr++].piece = Q_PROMO;
11468         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11469     }
11470     moveDatabase[movePtr].piece = piece;
11471     quickBoard[sq] = piece;
11472     movePtr++;
11473 }
11474
11475 int
11476 PackGame (Board board)
11477 {
11478     Move *newSpace = NULL;
11479     moveDatabase[movePtr].piece = 0; // terminate previous game
11480     if(movePtr > dataSize) {
11481         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11482         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11483         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11484         if(newSpace) {
11485             int i;
11486             Move *p = moveDatabase, *q = newSpace;
11487             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11488             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11489             moveDatabase = newSpace;
11490         } else { // calloc failed, we must be out of memory. Too bad...
11491             dataSize = 0; // prevent calloc events for all subsequent games
11492             return 0;     // and signal this one isn't cached
11493         }
11494     }
11495     movePtr++;
11496     MakePieceList(board, counts);
11497     return movePtr;
11498 }
11499
11500 int
11501 QuickCompare (Board board, int *minCounts, int *maxCounts)
11502 {   // compare according to search mode
11503     int r, f;
11504     switch(appData.searchMode)
11505     {
11506       case 1: // exact position match
11507         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11508         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11509             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11510         }
11511         break;
11512       case 2: // can have extra material on empty squares
11513         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11514             if(board[r][f] == EmptySquare) continue;
11515             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11516         }
11517         break;
11518       case 3: // material with exact Pawn structure
11519         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11520             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11521             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11522         } // fall through to material comparison
11523       case 4: // exact material
11524         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11525         break;
11526       case 6: // material range with given imbalance
11527         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11528         // fall through to range comparison
11529       case 5: // material range
11530         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11531     }
11532     return TRUE;
11533 }
11534
11535 int
11536 QuickScan (Board board, Move *move)
11537 {   // reconstruct game,and compare all positions in it
11538     int cnt=0, stretch=0, total = MakePieceList(board, counts), delayedKing = -1;
11539     do {
11540         int piece = move->piece;
11541         int to = move->to, from = pieceList[piece];
11542         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11543           if(!piece) return -1;
11544           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11545             piece = (++move)->piece;
11546             from = pieceList[piece];
11547             counts[pieceType[piece]]--;
11548             pieceType[piece] = (ChessSquare) move->to;
11549             counts[move->to]++;
11550           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11551             counts[pieceType[quickBoard[to]]]--;
11552             quickBoard[to] = 0; total--;
11553             move++;
11554             continue;
11555           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11556             int rook;
11557             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11558             from  = pieceList[piece]; // so this must be King
11559             quickBoard[from] = 0;
11560             pieceList[piece] = to;
11561             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11562             quickBoard[from] = 0; // rook
11563             quickBoard[to] = piece;
11564             to = move->to; piece = move->piece;
11565             goto aftercastle;
11566           }
11567         }
11568         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11569         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11570         quickBoard[from] = 0;
11571       aftercastle:
11572         quickBoard[to] = piece;
11573         pieceList[piece] = to;
11574         cnt++; turn ^= 3;
11575         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11576            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11577            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11578                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11579           ) {
11580             static int lastCounts[EmptySquare+1];
11581             int i;
11582             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11583             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11584         } else stretch = 0;
11585         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11586         move++; delayedKing = -1;
11587     } while(1);
11588 }
11589
11590 void
11591 InitSearch ()
11592 {
11593     int r, f;
11594     flipSearch = FALSE;
11595     CopyBoard(soughtBoard, boards[currentMove]);
11596     soughtTotal = MakePieceList(soughtBoard, maxSought);
11597     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11598     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11599     CopyBoard(reverseBoard, boards[currentMove]);
11600     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11601         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11602         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11603         reverseBoard[r][f] = piece;
11604     }
11605     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11606     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11607     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11608                  || (boards[currentMove][CASTLING][2] == NoRights || 
11609                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11610                  && (boards[currentMove][CASTLING][5] == NoRights || 
11611                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11612       ) {
11613         flipSearch = TRUE;
11614         CopyBoard(flipBoard, soughtBoard);
11615         CopyBoard(rotateBoard, reverseBoard);
11616         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11617             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11618             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11619         }
11620     }
11621     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11622     if(appData.searchMode >= 5) {
11623         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11624         MakePieceList(soughtBoard, minSought);
11625         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11626     }
11627     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11628         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11629 }
11630
11631 GameInfo dummyInfo;
11632
11633 int
11634 GameContainsPosition (FILE *f, ListGame *lg)
11635 {
11636     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11637     int fromX, fromY, toX, toY;
11638     char promoChar;
11639     static int initDone=FALSE;
11640
11641     // weed out games based on numerical tag comparison
11642     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11643     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11644     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11645     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11646     if(!initDone) {
11647         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11648         initDone = TRUE;
11649     }
11650     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11651     else CopyBoard(boards[scratch], initialPosition); // default start position
11652     if(lg->moves) {
11653         turn = btm + 1;
11654         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11655         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11656     }
11657     if(btm) plyNr++;
11658     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11659     fseek(f, lg->offset, 0);
11660     yynewfile(f);
11661     while(1) {
11662         yyboardindex = scratch;
11663         quickFlag = plyNr+1;
11664         next = Myylex();
11665         quickFlag = 0;
11666         switch(next) {
11667             case PGNTag:
11668                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11669             default:
11670                 continue;
11671
11672             case XBoardGame:
11673             case GNUChessGame:
11674                 if(plyNr) return -1; // after we have seen moves, this is for new game
11675               continue;
11676
11677             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11678             case ImpossibleMove:
11679             case WhiteWins: // game ends here with these four
11680             case BlackWins:
11681             case GameIsDrawn:
11682             case GameUnfinished:
11683                 return -1;
11684
11685             case IllegalMove:
11686                 if(appData.testLegality) return -1;
11687             case WhiteCapturesEnPassant:
11688             case BlackCapturesEnPassant:
11689             case WhitePromotion:
11690             case BlackPromotion:
11691             case WhiteNonPromotion:
11692             case BlackNonPromotion:
11693             case NormalMove:
11694             case WhiteKingSideCastle:
11695             case WhiteQueenSideCastle:
11696             case BlackKingSideCastle:
11697             case BlackQueenSideCastle:
11698             case WhiteKingSideCastleWild:
11699             case WhiteQueenSideCastleWild:
11700             case BlackKingSideCastleWild:
11701             case BlackQueenSideCastleWild:
11702             case WhiteHSideCastleFR:
11703             case WhiteASideCastleFR:
11704             case BlackHSideCastleFR:
11705             case BlackASideCastleFR:
11706                 fromX = currentMoveString[0] - AAA;
11707                 fromY = currentMoveString[1] - ONE;
11708                 toX = currentMoveString[2] - AAA;
11709                 toY = currentMoveString[3] - ONE;
11710                 promoChar = currentMoveString[4];
11711                 break;
11712             case WhiteDrop:
11713             case BlackDrop:
11714                 fromX = next == WhiteDrop ?
11715                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11716                   (int) CharToPiece(ToLower(currentMoveString[0]));
11717                 fromY = DROP_RANK;
11718                 toX = currentMoveString[2] - AAA;
11719                 toY = currentMoveString[3] - ONE;
11720                 promoChar = 0;
11721                 break;
11722         }
11723         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11724         plyNr++;
11725         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11726         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11727         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11728         if(appData.findMirror) {
11729             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11730             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11731         }
11732     }
11733 }
11734
11735 /* Load the nth game from open file f */
11736 int
11737 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11738 {
11739     ChessMove cm;
11740     char buf[MSG_SIZ];
11741     int gn = gameNumber;
11742     ListGame *lg = NULL;
11743     int numPGNTags = 0;
11744     int err, pos = -1;
11745     GameMode oldGameMode;
11746     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11747
11748     if (appData.debugMode)
11749         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11750
11751     if (gameMode == Training )
11752         SetTrainingModeOff();
11753
11754     oldGameMode = gameMode;
11755     if (gameMode != BeginningOfGame) {
11756       Reset(FALSE, TRUE);
11757     }
11758
11759     gameFileFP = f;
11760     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11761         fclose(lastLoadGameFP);
11762     }
11763
11764     if (useList) {
11765         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11766
11767         if (lg) {
11768             fseek(f, lg->offset, 0);
11769             GameListHighlight(gameNumber);
11770             pos = lg->position;
11771             gn = 1;
11772         }
11773         else {
11774             DisplayError(_("Game number out of range"), 0);
11775             return FALSE;
11776         }
11777     } else {
11778         GameListDestroy();
11779         if (fseek(f, 0, 0) == -1) {
11780             if (f == lastLoadGameFP ?
11781                 gameNumber == lastLoadGameNumber + 1 :
11782                 gameNumber == 1) {
11783                 gn = 1;
11784             } else {
11785                 DisplayError(_("Can't seek on game file"), 0);
11786                 return FALSE;
11787             }
11788         }
11789     }
11790     lastLoadGameFP = f;
11791     lastLoadGameNumber = gameNumber;
11792     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11793     lastLoadGameUseList = useList;
11794
11795     yynewfile(f);
11796
11797     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11798       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11799                 lg->gameInfo.black);
11800             DisplayTitle(buf);
11801     } else if (*title != NULLCHAR) {
11802         if (gameNumber > 1) {
11803           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11804             DisplayTitle(buf);
11805         } else {
11806             DisplayTitle(title);
11807         }
11808     }
11809
11810     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11811         gameMode = PlayFromGameFile;
11812         ModeHighlight();
11813     }
11814
11815     currentMove = forwardMostMove = backwardMostMove = 0;
11816     CopyBoard(boards[0], initialPosition);
11817     StopClocks();
11818
11819     /*
11820      * Skip the first gn-1 games in the file.
11821      * Also skip over anything that precedes an identifiable
11822      * start of game marker, to avoid being confused by
11823      * garbage at the start of the file.  Currently
11824      * recognized start of game markers are the move number "1",
11825      * the pattern "gnuchess .* game", the pattern
11826      * "^[#;%] [^ ]* game file", and a PGN tag block.
11827      * A game that starts with one of the latter two patterns
11828      * will also have a move number 1, possibly
11829      * following a position diagram.
11830      * 5-4-02: Let's try being more lenient and allowing a game to
11831      * start with an unnumbered move.  Does that break anything?
11832      */
11833     cm = lastLoadGameStart = EndOfFile;
11834     while (gn > 0) {
11835         yyboardindex = forwardMostMove;
11836         cm = (ChessMove) Myylex();
11837         switch (cm) {
11838           case EndOfFile:
11839             if (cmailMsgLoaded) {
11840                 nCmailGames = CMAIL_MAX_GAMES - gn;
11841             } else {
11842                 Reset(TRUE, TRUE);
11843                 DisplayError(_("Game not found in file"), 0);
11844             }
11845             return FALSE;
11846
11847           case GNUChessGame:
11848           case XBoardGame:
11849             gn--;
11850             lastLoadGameStart = cm;
11851             break;
11852
11853           case MoveNumberOne:
11854             switch (lastLoadGameStart) {
11855               case GNUChessGame:
11856               case XBoardGame:
11857               case PGNTag:
11858                 break;
11859               case MoveNumberOne:
11860               case EndOfFile:
11861                 gn--;           /* count this game */
11862                 lastLoadGameStart = cm;
11863                 break;
11864               default:
11865                 /* impossible */
11866                 break;
11867             }
11868             break;
11869
11870           case PGNTag:
11871             switch (lastLoadGameStart) {
11872               case GNUChessGame:
11873               case PGNTag:
11874               case MoveNumberOne:
11875               case EndOfFile:
11876                 gn--;           /* count this game */
11877                 lastLoadGameStart = cm;
11878                 break;
11879               case XBoardGame:
11880                 lastLoadGameStart = cm; /* game counted already */
11881                 break;
11882               default:
11883                 /* impossible */
11884                 break;
11885             }
11886             if (gn > 0) {
11887                 do {
11888                     yyboardindex = forwardMostMove;
11889                     cm = (ChessMove) Myylex();
11890                 } while (cm == PGNTag || cm == Comment);
11891             }
11892             break;
11893
11894           case WhiteWins:
11895           case BlackWins:
11896           case GameIsDrawn:
11897             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11898                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11899                     != CMAIL_OLD_RESULT) {
11900                     nCmailResults ++ ;
11901                     cmailResult[  CMAIL_MAX_GAMES
11902                                 - gn - 1] = CMAIL_OLD_RESULT;
11903                 }
11904             }
11905             break;
11906
11907           case NormalMove:
11908             /* Only a NormalMove can be at the start of a game
11909              * without a position diagram. */
11910             if (lastLoadGameStart == EndOfFile ) {
11911               gn--;
11912               lastLoadGameStart = MoveNumberOne;
11913             }
11914             break;
11915
11916           default:
11917             break;
11918         }
11919     }
11920
11921     if (appData.debugMode)
11922       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11923
11924     if (cm == XBoardGame) {
11925         /* Skip any header junk before position diagram and/or move 1 */
11926         for (;;) {
11927             yyboardindex = forwardMostMove;
11928             cm = (ChessMove) Myylex();
11929
11930             if (cm == EndOfFile ||
11931                 cm == GNUChessGame || cm == XBoardGame) {
11932                 /* Empty game; pretend end-of-file and handle later */
11933                 cm = EndOfFile;
11934                 break;
11935             }
11936
11937             if (cm == MoveNumberOne || cm == PositionDiagram ||
11938                 cm == PGNTag || cm == Comment)
11939               break;
11940         }
11941     } else if (cm == GNUChessGame) {
11942         if (gameInfo.event != NULL) {
11943             free(gameInfo.event);
11944         }
11945         gameInfo.event = StrSave(yy_text);
11946     }
11947
11948     startedFromSetupPosition = FALSE;
11949     while (cm == PGNTag) {
11950         if (appData.debugMode)
11951           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11952         err = ParsePGNTag(yy_text, &gameInfo);
11953         if (!err) numPGNTags++;
11954
11955         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11956         if(gameInfo.variant != oldVariant) {
11957             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11958             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11959             InitPosition(TRUE);
11960             oldVariant = gameInfo.variant;
11961             if (appData.debugMode)
11962               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11963         }
11964
11965
11966         if (gameInfo.fen != NULL) {
11967           Board initial_position;
11968           startedFromSetupPosition = TRUE;
11969           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11970             Reset(TRUE, TRUE);
11971             DisplayError(_("Bad FEN position in file"), 0);
11972             return FALSE;
11973           }
11974           CopyBoard(boards[0], initial_position);
11975           if (blackPlaysFirst) {
11976             currentMove = forwardMostMove = backwardMostMove = 1;
11977             CopyBoard(boards[1], initial_position);
11978             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11979             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11980             timeRemaining[0][1] = whiteTimeRemaining;
11981             timeRemaining[1][1] = blackTimeRemaining;
11982             if (commentList[0] != NULL) {
11983               commentList[1] = commentList[0];
11984               commentList[0] = NULL;
11985             }
11986           } else {
11987             currentMove = forwardMostMove = backwardMostMove = 0;
11988           }
11989           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11990           {   int i;
11991               initialRulePlies = FENrulePlies;
11992               for( i=0; i< nrCastlingRights; i++ )
11993                   initialRights[i] = initial_position[CASTLING][i];
11994           }
11995           yyboardindex = forwardMostMove;
11996           free(gameInfo.fen);
11997           gameInfo.fen = NULL;
11998         }
11999
12000         yyboardindex = forwardMostMove;
12001         cm = (ChessMove) Myylex();
12002
12003         /* Handle comments interspersed among the tags */
12004         while (cm == Comment) {
12005             char *p;
12006             if (appData.debugMode)
12007               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12008             p = yy_text;
12009             AppendComment(currentMove, p, FALSE);
12010             yyboardindex = forwardMostMove;
12011             cm = (ChessMove) Myylex();
12012         }
12013     }
12014
12015     /* don't rely on existence of Event tag since if game was
12016      * pasted from clipboard the Event tag may not exist
12017      */
12018     if (numPGNTags > 0){
12019         char *tags;
12020         if (gameInfo.variant == VariantNormal) {
12021           VariantClass v = StringToVariant(gameInfo.event);
12022           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12023           if(v < VariantShogi) gameInfo.variant = v;
12024         }
12025         if (!matchMode) {
12026           if( appData.autoDisplayTags ) {
12027             tags = PGNTags(&gameInfo);
12028             TagsPopUp(tags, CmailMsg());
12029             free(tags);
12030           }
12031         }
12032     } else {
12033         /* Make something up, but don't display it now */
12034         SetGameInfo();
12035         TagsPopDown();
12036     }
12037
12038     if (cm == PositionDiagram) {
12039         int i, j;
12040         char *p;
12041         Board initial_position;
12042
12043         if (appData.debugMode)
12044           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12045
12046         if (!startedFromSetupPosition) {
12047             p = yy_text;
12048             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12049               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12050                 switch (*p) {
12051                   case '{':
12052                   case '[':
12053                   case '-':
12054                   case ' ':
12055                   case '\t':
12056                   case '\n':
12057                   case '\r':
12058                     break;
12059                   default:
12060                     initial_position[i][j++] = CharToPiece(*p);
12061                     break;
12062                 }
12063             while (*p == ' ' || *p == '\t' ||
12064                    *p == '\n' || *p == '\r') p++;
12065
12066             if (strncmp(p, "black", strlen("black"))==0)
12067               blackPlaysFirst = TRUE;
12068             else
12069               blackPlaysFirst = FALSE;
12070             startedFromSetupPosition = TRUE;
12071
12072             CopyBoard(boards[0], initial_position);
12073             if (blackPlaysFirst) {
12074                 currentMove = forwardMostMove = backwardMostMove = 1;
12075                 CopyBoard(boards[1], initial_position);
12076                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12077                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12078                 timeRemaining[0][1] = whiteTimeRemaining;
12079                 timeRemaining[1][1] = blackTimeRemaining;
12080                 if (commentList[0] != NULL) {
12081                     commentList[1] = commentList[0];
12082                     commentList[0] = NULL;
12083                 }
12084             } else {
12085                 currentMove = forwardMostMove = backwardMostMove = 0;
12086             }
12087         }
12088         yyboardindex = forwardMostMove;
12089         cm = (ChessMove) Myylex();
12090     }
12091
12092     if (first.pr == NoProc) {
12093         StartChessProgram(&first);
12094     }
12095     InitChessProgram(&first, FALSE);
12096     SendToProgram("force\n", &first);
12097     if (startedFromSetupPosition) {
12098         SendBoard(&first, forwardMostMove);
12099     if (appData.debugMode) {
12100         fprintf(debugFP, "Load Game\n");
12101     }
12102         DisplayBothClocks();
12103     }
12104
12105     /* [HGM] server: flag to write setup moves in broadcast file as one */
12106     loadFlag = appData.suppressLoadMoves;
12107
12108     while (cm == Comment) {
12109         char *p;
12110         if (appData.debugMode)
12111           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12112         p = yy_text;
12113         AppendComment(currentMove, p, FALSE);
12114         yyboardindex = forwardMostMove;
12115         cm = (ChessMove) Myylex();
12116     }
12117
12118     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12119         cm == WhiteWins || cm == BlackWins ||
12120         cm == GameIsDrawn || cm == GameUnfinished) {
12121         DisplayMessage("", _("No moves in game"));
12122         if (cmailMsgLoaded) {
12123             if (appData.debugMode)
12124               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12125             ClearHighlights();
12126             flipView = FALSE;
12127         }
12128         DrawPosition(FALSE, boards[currentMove]);
12129         DisplayBothClocks();
12130         gameMode = EditGame;
12131         ModeHighlight();
12132         gameFileFP = NULL;
12133         cmailOldMove = 0;
12134         return TRUE;
12135     }
12136
12137     // [HGM] PV info: routine tests if comment empty
12138     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12139         DisplayComment(currentMove - 1, commentList[currentMove]);
12140     }
12141     if (!matchMode && appData.timeDelay != 0)
12142       DrawPosition(FALSE, boards[currentMove]);
12143
12144     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12145       programStats.ok_to_send = 1;
12146     }
12147
12148     /* if the first token after the PGN tags is a move
12149      * and not move number 1, retrieve it from the parser
12150      */
12151     if (cm != MoveNumberOne)
12152         LoadGameOneMove(cm);
12153
12154     /* load the remaining moves from the file */
12155     while (LoadGameOneMove(EndOfFile)) {
12156       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12157       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12158     }
12159
12160     /* rewind to the start of the game */
12161     currentMove = backwardMostMove;
12162
12163     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12164
12165     if (oldGameMode == AnalyzeFile ||
12166         oldGameMode == AnalyzeMode) {
12167       AnalyzeFileEvent();
12168     }
12169
12170     if (!matchMode && pos > 0) {
12171         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12172     } else
12173     if (matchMode || appData.timeDelay == 0) {
12174       ToEndEvent();
12175     } else if (appData.timeDelay > 0) {
12176       AutoPlayGameLoop();
12177     }
12178
12179     if (appData.debugMode)
12180         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12181
12182     loadFlag = 0; /* [HGM] true game starts */
12183     return TRUE;
12184 }
12185
12186 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12187 int
12188 ReloadPosition (int offset)
12189 {
12190     int positionNumber = lastLoadPositionNumber + offset;
12191     if (lastLoadPositionFP == NULL) {
12192         DisplayError(_("No position has been loaded yet"), 0);
12193         return FALSE;
12194     }
12195     if (positionNumber <= 0) {
12196         DisplayError(_("Can't back up any further"), 0);
12197         return FALSE;
12198     }
12199     return LoadPosition(lastLoadPositionFP, positionNumber,
12200                         lastLoadPositionTitle);
12201 }
12202
12203 /* Load the nth position from the given file */
12204 int
12205 LoadPositionFromFile (char *filename, int n, char *title)
12206 {
12207     FILE *f;
12208     char buf[MSG_SIZ];
12209
12210     if (strcmp(filename, "-") == 0) {
12211         return LoadPosition(stdin, n, "stdin");
12212     } else {
12213         f = fopen(filename, "rb");
12214         if (f == NULL) {
12215             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12216             DisplayError(buf, errno);
12217             return FALSE;
12218         } else {
12219             return LoadPosition(f, n, title);
12220         }
12221     }
12222 }
12223
12224 /* Load the nth position from the given open file, and close it */
12225 int
12226 LoadPosition (FILE *f, int positionNumber, char *title)
12227 {
12228     char *p, line[MSG_SIZ];
12229     Board initial_position;
12230     int i, j, fenMode, pn;
12231
12232     if (gameMode == Training )
12233         SetTrainingModeOff();
12234
12235     if (gameMode != BeginningOfGame) {
12236         Reset(FALSE, TRUE);
12237     }
12238     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12239         fclose(lastLoadPositionFP);
12240     }
12241     if (positionNumber == 0) positionNumber = 1;
12242     lastLoadPositionFP = f;
12243     lastLoadPositionNumber = positionNumber;
12244     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12245     if (first.pr == NoProc && !appData.noChessProgram) {
12246       StartChessProgram(&first);
12247       InitChessProgram(&first, FALSE);
12248     }
12249     pn = positionNumber;
12250     if (positionNumber < 0) {
12251         /* Negative position number means to seek to that byte offset */
12252         if (fseek(f, -positionNumber, 0) == -1) {
12253             DisplayError(_("Can't seek on position file"), 0);
12254             return FALSE;
12255         };
12256         pn = 1;
12257     } else {
12258         if (fseek(f, 0, 0) == -1) {
12259             if (f == lastLoadPositionFP ?
12260                 positionNumber == lastLoadPositionNumber + 1 :
12261                 positionNumber == 1) {
12262                 pn = 1;
12263             } else {
12264                 DisplayError(_("Can't seek on position file"), 0);
12265                 return FALSE;
12266             }
12267         }
12268     }
12269     /* See if this file is FEN or old-style xboard */
12270     if (fgets(line, MSG_SIZ, f) == NULL) {
12271         DisplayError(_("Position not found in file"), 0);
12272         return FALSE;
12273     }
12274     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12275     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12276
12277     if (pn >= 2) {
12278         if (fenMode || line[0] == '#') pn--;
12279         while (pn > 0) {
12280             /* skip positions before number pn */
12281             if (fgets(line, MSG_SIZ, f) == NULL) {
12282                 Reset(TRUE, TRUE);
12283                 DisplayError(_("Position not found in file"), 0);
12284                 return FALSE;
12285             }
12286             if (fenMode || line[0] == '#') pn--;
12287         }
12288     }
12289
12290     if (fenMode) {
12291         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12292             DisplayError(_("Bad FEN position in file"), 0);
12293             return FALSE;
12294         }
12295     } else {
12296         (void) fgets(line, MSG_SIZ, f);
12297         (void) fgets(line, MSG_SIZ, f);
12298
12299         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12300             (void) fgets(line, MSG_SIZ, f);
12301             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12302                 if (*p == ' ')
12303                   continue;
12304                 initial_position[i][j++] = CharToPiece(*p);
12305             }
12306         }
12307
12308         blackPlaysFirst = FALSE;
12309         if (!feof(f)) {
12310             (void) fgets(line, MSG_SIZ, f);
12311             if (strncmp(line, "black", strlen("black"))==0)
12312               blackPlaysFirst = TRUE;
12313         }
12314     }
12315     startedFromSetupPosition = TRUE;
12316
12317     CopyBoard(boards[0], initial_position);
12318     if (blackPlaysFirst) {
12319         currentMove = forwardMostMove = backwardMostMove = 1;
12320         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12321         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12322         CopyBoard(boards[1], initial_position);
12323         DisplayMessage("", _("Black to play"));
12324     } else {
12325         currentMove = forwardMostMove = backwardMostMove = 0;
12326         DisplayMessage("", _("White to play"));
12327     }
12328     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12329     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12330         SendToProgram("force\n", &first);
12331         SendBoard(&first, forwardMostMove);
12332     }
12333     if (appData.debugMode) {
12334 int i, j;
12335   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12336   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12337         fprintf(debugFP, "Load Position\n");
12338     }
12339
12340     if (positionNumber > 1) {
12341       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12342         DisplayTitle(line);
12343     } else {
12344         DisplayTitle(title);
12345     }
12346     gameMode = EditGame;
12347     ModeHighlight();
12348     ResetClocks();
12349     timeRemaining[0][1] = whiteTimeRemaining;
12350     timeRemaining[1][1] = blackTimeRemaining;
12351     DrawPosition(FALSE, boards[currentMove]);
12352
12353     return TRUE;
12354 }
12355
12356
12357 void
12358 CopyPlayerNameIntoFileName (char **dest, char *src)
12359 {
12360     while (*src != NULLCHAR && *src != ',') {
12361         if (*src == ' ') {
12362             *(*dest)++ = '_';
12363             src++;
12364         } else {
12365             *(*dest)++ = *src++;
12366         }
12367     }
12368 }
12369
12370 char *
12371 DefaultFileName (char *ext)
12372 {
12373     static char def[MSG_SIZ];
12374     char *p;
12375
12376     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12377         p = def;
12378         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12379         *p++ = '-';
12380         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12381         *p++ = '.';
12382         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12383     } else {
12384         def[0] = NULLCHAR;
12385     }
12386     return def;
12387 }
12388
12389 /* Save the current game to the given file */
12390 int
12391 SaveGameToFile (char *filename, int append)
12392 {
12393     FILE *f;
12394     char buf[MSG_SIZ];
12395     int result, i, t,tot=0;
12396
12397     if (strcmp(filename, "-") == 0) {
12398         return SaveGame(stdout, 0, NULL);
12399     } else {
12400         for(i=0; i<10; i++) { // upto 10 tries
12401              f = fopen(filename, append ? "a" : "w");
12402              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12403              if(f || errno != 13) break;
12404              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12405              tot += t;
12406         }
12407         if (f == NULL) {
12408             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12409             DisplayError(buf, errno);
12410             return FALSE;
12411         } else {
12412             safeStrCpy(buf, lastMsg, MSG_SIZ);
12413             DisplayMessage(_("Waiting for access to save file"), "");
12414             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12415             DisplayMessage(_("Saving game"), "");
12416             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12417             result = SaveGame(f, 0, NULL);
12418             DisplayMessage(buf, "");
12419             return result;
12420         }
12421     }
12422 }
12423
12424 char *
12425 SavePart (char *str)
12426 {
12427     static char buf[MSG_SIZ];
12428     char *p;
12429
12430     p = strchr(str, ' ');
12431     if (p == NULL) return str;
12432     strncpy(buf, str, p - str);
12433     buf[p - str] = NULLCHAR;
12434     return buf;
12435 }
12436
12437 #define PGN_MAX_LINE 75
12438
12439 #define PGN_SIDE_WHITE  0
12440 #define PGN_SIDE_BLACK  1
12441
12442 static int
12443 FindFirstMoveOutOfBook (int side)
12444 {
12445     int result = -1;
12446
12447     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12448         int index = backwardMostMove;
12449         int has_book_hit = 0;
12450
12451         if( (index % 2) != side ) {
12452             index++;
12453         }
12454
12455         while( index < forwardMostMove ) {
12456             /* Check to see if engine is in book */
12457             int depth = pvInfoList[index].depth;
12458             int score = pvInfoList[index].score;
12459             int in_book = 0;
12460
12461             if( depth <= 2 ) {
12462                 in_book = 1;
12463             }
12464             else if( score == 0 && depth == 63 ) {
12465                 in_book = 1; /* Zappa */
12466             }
12467             else if( score == 2 && depth == 99 ) {
12468                 in_book = 1; /* Abrok */
12469             }
12470
12471             has_book_hit += in_book;
12472
12473             if( ! in_book ) {
12474                 result = index;
12475
12476                 break;
12477             }
12478
12479             index += 2;
12480         }
12481     }
12482
12483     return result;
12484 }
12485
12486 void
12487 GetOutOfBookInfo (char * buf)
12488 {
12489     int oob[2];
12490     int i;
12491     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12492
12493     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12494     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12495
12496     *buf = '\0';
12497
12498     if( oob[0] >= 0 || oob[1] >= 0 ) {
12499         for( i=0; i<2; i++ ) {
12500             int idx = oob[i];
12501
12502             if( idx >= 0 ) {
12503                 if( i > 0 && oob[0] >= 0 ) {
12504                     strcat( buf, "   " );
12505                 }
12506
12507                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12508                 sprintf( buf+strlen(buf), "%s%.2f",
12509                     pvInfoList[idx].score >= 0 ? "+" : "",
12510                     pvInfoList[idx].score / 100.0 );
12511             }
12512         }
12513     }
12514 }
12515
12516 /* Save game in PGN style and close the file */
12517 int
12518 SaveGamePGN (FILE *f)
12519 {
12520     int i, offset, linelen, newblock;
12521     time_t tm;
12522 //    char *movetext;
12523     char numtext[32];
12524     int movelen, numlen, blank;
12525     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12526
12527     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12528
12529     tm = time((time_t *) NULL);
12530
12531     PrintPGNTags(f, &gameInfo);
12532
12533     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12534
12535     if (backwardMostMove > 0 || startedFromSetupPosition) {
12536         char *fen = PositionToFEN(backwardMostMove, NULL);
12537         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12538         fprintf(f, "\n{--------------\n");
12539         PrintPosition(f, backwardMostMove);
12540         fprintf(f, "--------------}\n");
12541         free(fen);
12542     }
12543     else {
12544         /* [AS] Out of book annotation */
12545         if( appData.saveOutOfBookInfo ) {
12546             char buf[64];
12547
12548             GetOutOfBookInfo( buf );
12549
12550             if( buf[0] != '\0' ) {
12551                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12552             }
12553         }
12554
12555         fprintf(f, "\n");
12556     }
12557
12558     i = backwardMostMove;
12559     linelen = 0;
12560     newblock = TRUE;
12561
12562     while (i < forwardMostMove) {
12563         /* Print comments preceding this move */
12564         if (commentList[i] != NULL) {
12565             if (linelen > 0) fprintf(f, "\n");
12566             fprintf(f, "%s", commentList[i]);
12567             linelen = 0;
12568             newblock = TRUE;
12569         }
12570
12571         /* Format move number */
12572         if ((i % 2) == 0)
12573           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12574         else
12575           if (newblock)
12576             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12577           else
12578             numtext[0] = NULLCHAR;
12579
12580         numlen = strlen(numtext);
12581         newblock = FALSE;
12582
12583         /* Print move number */
12584         blank = linelen > 0 && numlen > 0;
12585         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12586             fprintf(f, "\n");
12587             linelen = 0;
12588             blank = 0;
12589         }
12590         if (blank) {
12591             fprintf(f, " ");
12592             linelen++;
12593         }
12594         fprintf(f, "%s", numtext);
12595         linelen += numlen;
12596
12597         /* Get move */
12598         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12599         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12600
12601         /* Print move */
12602         blank = linelen > 0 && movelen > 0;
12603         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12604             fprintf(f, "\n");
12605             linelen = 0;
12606             blank = 0;
12607         }
12608         if (blank) {
12609             fprintf(f, " ");
12610             linelen++;
12611         }
12612         fprintf(f, "%s", move_buffer);
12613         linelen += movelen;
12614
12615         /* [AS] Add PV info if present */
12616         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12617             /* [HGM] add time */
12618             char buf[MSG_SIZ]; int seconds;
12619
12620             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12621
12622             if( seconds <= 0)
12623               buf[0] = 0;
12624             else
12625               if( seconds < 30 )
12626                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12627               else
12628                 {
12629                   seconds = (seconds + 4)/10; // round to full seconds
12630                   if( seconds < 60 )
12631                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12632                   else
12633                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12634                 }
12635
12636             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12637                       pvInfoList[i].score >= 0 ? "+" : "",
12638                       pvInfoList[i].score / 100.0,
12639                       pvInfoList[i].depth,
12640                       buf );
12641
12642             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12643
12644             /* Print score/depth */
12645             blank = linelen > 0 && movelen > 0;
12646             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12647                 fprintf(f, "\n");
12648                 linelen = 0;
12649                 blank = 0;
12650             }
12651             if (blank) {
12652                 fprintf(f, " ");
12653                 linelen++;
12654             }
12655             fprintf(f, "%s", move_buffer);
12656             linelen += movelen;
12657         }
12658
12659         i++;
12660     }
12661
12662     /* Start a new line */
12663     if (linelen > 0) fprintf(f, "\n");
12664
12665     /* Print comments after last move */
12666     if (commentList[i] != NULL) {
12667         fprintf(f, "%s\n", commentList[i]);
12668     }
12669
12670     /* Print result */
12671     if (gameInfo.resultDetails != NULL &&
12672         gameInfo.resultDetails[0] != NULLCHAR) {
12673         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12674                 PGNResult(gameInfo.result));
12675     } else {
12676         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12677     }
12678
12679     fclose(f);
12680     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12681     return TRUE;
12682 }
12683
12684 /* Save game in old style and close the file */
12685 int
12686 SaveGameOldStyle (FILE *f)
12687 {
12688     int i, offset;
12689     time_t tm;
12690
12691     tm = time((time_t *) NULL);
12692
12693     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12694     PrintOpponents(f);
12695
12696     if (backwardMostMove > 0 || startedFromSetupPosition) {
12697         fprintf(f, "\n[--------------\n");
12698         PrintPosition(f, backwardMostMove);
12699         fprintf(f, "--------------]\n");
12700     } else {
12701         fprintf(f, "\n");
12702     }
12703
12704     i = backwardMostMove;
12705     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12706
12707     while (i < forwardMostMove) {
12708         if (commentList[i] != NULL) {
12709             fprintf(f, "[%s]\n", commentList[i]);
12710         }
12711
12712         if ((i % 2) == 1) {
12713             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12714             i++;
12715         } else {
12716             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12717             i++;
12718             if (commentList[i] != NULL) {
12719                 fprintf(f, "\n");
12720                 continue;
12721             }
12722             if (i >= forwardMostMove) {
12723                 fprintf(f, "\n");
12724                 break;
12725             }
12726             fprintf(f, "%s\n", parseList[i]);
12727             i++;
12728         }
12729     }
12730
12731     if (commentList[i] != NULL) {
12732         fprintf(f, "[%s]\n", commentList[i]);
12733     }
12734
12735     /* This isn't really the old style, but it's close enough */
12736     if (gameInfo.resultDetails != NULL &&
12737         gameInfo.resultDetails[0] != NULLCHAR) {
12738         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12739                 gameInfo.resultDetails);
12740     } else {
12741         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12742     }
12743
12744     fclose(f);
12745     return TRUE;
12746 }
12747
12748 /* Save the current game to open file f and close the file */
12749 int
12750 SaveGame (FILE *f, int dummy, char *dummy2)
12751 {
12752     if (gameMode == EditPosition) EditPositionDone(TRUE);
12753     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12754     if (appData.oldSaveStyle)
12755       return SaveGameOldStyle(f);
12756     else
12757       return SaveGamePGN(f);
12758 }
12759
12760 /* Save the current position to the given file */
12761 int
12762 SavePositionToFile (char *filename)
12763 {
12764     FILE *f;
12765     char buf[MSG_SIZ];
12766
12767     if (strcmp(filename, "-") == 0) {
12768         return SavePosition(stdout, 0, NULL);
12769     } else {
12770         f = fopen(filename, "a");
12771         if (f == NULL) {
12772             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12773             DisplayError(buf, errno);
12774             return FALSE;
12775         } else {
12776             safeStrCpy(buf, lastMsg, MSG_SIZ);
12777             DisplayMessage(_("Waiting for access to save file"), "");
12778             flock(fileno(f), LOCK_EX); // [HGM] lock
12779             DisplayMessage(_("Saving position"), "");
12780             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12781             SavePosition(f, 0, NULL);
12782             DisplayMessage(buf, "");
12783             return TRUE;
12784         }
12785     }
12786 }
12787
12788 /* Save the current position to the given open file and close the file */
12789 int
12790 SavePosition (FILE *f, int dummy, char *dummy2)
12791 {
12792     time_t tm;
12793     char *fen;
12794
12795     if (gameMode == EditPosition) EditPositionDone(TRUE);
12796     if (appData.oldSaveStyle) {
12797         tm = time((time_t *) NULL);
12798
12799         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12800         PrintOpponents(f);
12801         fprintf(f, "[--------------\n");
12802         PrintPosition(f, currentMove);
12803         fprintf(f, "--------------]\n");
12804     } else {
12805         fen = PositionToFEN(currentMove, NULL);
12806         fprintf(f, "%s\n", fen);
12807         free(fen);
12808     }
12809     fclose(f);
12810     return TRUE;
12811 }
12812
12813 void
12814 ReloadCmailMsgEvent (int unregister)
12815 {
12816 #if !WIN32
12817     static char *inFilename = NULL;
12818     static char *outFilename;
12819     int i;
12820     struct stat inbuf, outbuf;
12821     int status;
12822
12823     /* Any registered moves are unregistered if unregister is set, */
12824     /* i.e. invoked by the signal handler */
12825     if (unregister) {
12826         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12827             cmailMoveRegistered[i] = FALSE;
12828             if (cmailCommentList[i] != NULL) {
12829                 free(cmailCommentList[i]);
12830                 cmailCommentList[i] = NULL;
12831             }
12832         }
12833         nCmailMovesRegistered = 0;
12834     }
12835
12836     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12837         cmailResult[i] = CMAIL_NOT_RESULT;
12838     }
12839     nCmailResults = 0;
12840
12841     if (inFilename == NULL) {
12842         /* Because the filenames are static they only get malloced once  */
12843         /* and they never get freed                                      */
12844         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12845         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12846
12847         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12848         sprintf(outFilename, "%s.out", appData.cmailGameName);
12849     }
12850
12851     status = stat(outFilename, &outbuf);
12852     if (status < 0) {
12853         cmailMailedMove = FALSE;
12854     } else {
12855         status = stat(inFilename, &inbuf);
12856         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12857     }
12858
12859     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12860        counts the games, notes how each one terminated, etc.
12861
12862        It would be nice to remove this kludge and instead gather all
12863        the information while building the game list.  (And to keep it
12864        in the game list nodes instead of having a bunch of fixed-size
12865        parallel arrays.)  Note this will require getting each game's
12866        termination from the PGN tags, as the game list builder does
12867        not process the game moves.  --mann
12868        */
12869     cmailMsgLoaded = TRUE;
12870     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12871
12872     /* Load first game in the file or popup game menu */
12873     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12874
12875 #endif /* !WIN32 */
12876     return;
12877 }
12878
12879 int
12880 RegisterMove ()
12881 {
12882     FILE *f;
12883     char string[MSG_SIZ];
12884
12885     if (   cmailMailedMove
12886         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12887         return TRUE;            /* Allow free viewing  */
12888     }
12889
12890     /* Unregister move to ensure that we don't leave RegisterMove        */
12891     /* with the move registered when the conditions for registering no   */
12892     /* longer hold                                                       */
12893     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12894         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12895         nCmailMovesRegistered --;
12896
12897         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12898           {
12899               free(cmailCommentList[lastLoadGameNumber - 1]);
12900               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12901           }
12902     }
12903
12904     if (cmailOldMove == -1) {
12905         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12906         return FALSE;
12907     }
12908
12909     if (currentMove > cmailOldMove + 1) {
12910         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12911         return FALSE;
12912     }
12913
12914     if (currentMove < cmailOldMove) {
12915         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12916         return FALSE;
12917     }
12918
12919     if (forwardMostMove > currentMove) {
12920         /* Silently truncate extra moves */
12921         TruncateGame();
12922     }
12923
12924     if (   (currentMove == cmailOldMove + 1)
12925         || (   (currentMove == cmailOldMove)
12926             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12927                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12928         if (gameInfo.result != GameUnfinished) {
12929             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12930         }
12931
12932         if (commentList[currentMove] != NULL) {
12933             cmailCommentList[lastLoadGameNumber - 1]
12934               = StrSave(commentList[currentMove]);
12935         }
12936         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12937
12938         if (appData.debugMode)
12939           fprintf(debugFP, "Saving %s for game %d\n",
12940                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12941
12942         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12943
12944         f = fopen(string, "w");
12945         if (appData.oldSaveStyle) {
12946             SaveGameOldStyle(f); /* also closes the file */
12947
12948             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12949             f = fopen(string, "w");
12950             SavePosition(f, 0, NULL); /* also closes the file */
12951         } else {
12952             fprintf(f, "{--------------\n");
12953             PrintPosition(f, currentMove);
12954             fprintf(f, "--------------}\n\n");
12955
12956             SaveGame(f, 0, NULL); /* also closes the file*/
12957         }
12958
12959         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12960         nCmailMovesRegistered ++;
12961     } else if (nCmailGames == 1) {
12962         DisplayError(_("You have not made a move yet"), 0);
12963         return FALSE;
12964     }
12965
12966     return TRUE;
12967 }
12968
12969 void
12970 MailMoveEvent ()
12971 {
12972 #if !WIN32
12973     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12974     FILE *commandOutput;
12975     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12976     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12977     int nBuffers;
12978     int i;
12979     int archived;
12980     char *arcDir;
12981
12982     if (! cmailMsgLoaded) {
12983         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12984         return;
12985     }
12986
12987     if (nCmailGames == nCmailResults) {
12988         DisplayError(_("No unfinished games"), 0);
12989         return;
12990     }
12991
12992 #if CMAIL_PROHIBIT_REMAIL
12993     if (cmailMailedMove) {
12994       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);
12995         DisplayError(msg, 0);
12996         return;
12997     }
12998 #endif
12999
13000     if (! (cmailMailedMove || RegisterMove())) return;
13001
13002     if (   cmailMailedMove
13003         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13004       snprintf(string, MSG_SIZ, partCommandString,
13005                appData.debugMode ? " -v" : "", appData.cmailGameName);
13006         commandOutput = popen(string, "r");
13007
13008         if (commandOutput == NULL) {
13009             DisplayError(_("Failed to invoke cmail"), 0);
13010         } else {
13011             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13012                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13013             }
13014             if (nBuffers > 1) {
13015                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13016                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13017                 nBytes = MSG_SIZ - 1;
13018             } else {
13019                 (void) memcpy(msg, buffer, nBytes);
13020             }
13021             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13022
13023             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13024                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13025
13026                 archived = TRUE;
13027                 for (i = 0; i < nCmailGames; i ++) {
13028                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13029                         archived = FALSE;
13030                     }
13031                 }
13032                 if (   archived
13033                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13034                         != NULL)) {
13035                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13036                            arcDir,
13037                            appData.cmailGameName,
13038                            gameInfo.date);
13039                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13040                     cmailMsgLoaded = FALSE;
13041                 }
13042             }
13043
13044             DisplayInformation(msg);
13045             pclose(commandOutput);
13046         }
13047     } else {
13048         if ((*cmailMsg) != '\0') {
13049             DisplayInformation(cmailMsg);
13050         }
13051     }
13052
13053     return;
13054 #endif /* !WIN32 */
13055 }
13056
13057 char *
13058 CmailMsg ()
13059 {
13060 #if WIN32
13061     return NULL;
13062 #else
13063     int  prependComma = 0;
13064     char number[5];
13065     char string[MSG_SIZ];       /* Space for game-list */
13066     int  i;
13067
13068     if (!cmailMsgLoaded) return "";
13069
13070     if (cmailMailedMove) {
13071       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13072     } else {
13073         /* Create a list of games left */
13074       snprintf(string, MSG_SIZ, "[");
13075         for (i = 0; i < nCmailGames; i ++) {
13076             if (! (   cmailMoveRegistered[i]
13077                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13078                 if (prependComma) {
13079                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13080                 } else {
13081                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13082                     prependComma = 1;
13083                 }
13084
13085                 strcat(string, number);
13086             }
13087         }
13088         strcat(string, "]");
13089
13090         if (nCmailMovesRegistered + nCmailResults == 0) {
13091             switch (nCmailGames) {
13092               case 1:
13093                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13094                 break;
13095
13096               case 2:
13097                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13098                 break;
13099
13100               default:
13101                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13102                          nCmailGames);
13103                 break;
13104             }
13105         } else {
13106             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13107               case 1:
13108                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13109                          string);
13110                 break;
13111
13112               case 0:
13113                 if (nCmailResults == nCmailGames) {
13114                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13115                 } else {
13116                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13117                 }
13118                 break;
13119
13120               default:
13121                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13122                          string);
13123             }
13124         }
13125     }
13126     return cmailMsg;
13127 #endif /* WIN32 */
13128 }
13129
13130 void
13131 ResetGameEvent ()
13132 {
13133     if (gameMode == Training)
13134       SetTrainingModeOff();
13135
13136     Reset(TRUE, TRUE);
13137     cmailMsgLoaded = FALSE;
13138     if (appData.icsActive) {
13139       SendToICS(ics_prefix);
13140       SendToICS("refresh\n");
13141     }
13142 }
13143
13144 void
13145 ExitEvent (int status)
13146 {
13147     exiting++;
13148     if (exiting > 2) {
13149       /* Give up on clean exit */
13150       exit(status);
13151     }
13152     if (exiting > 1) {
13153       /* Keep trying for clean exit */
13154       return;
13155     }
13156
13157     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13158
13159     if (telnetISR != NULL) {
13160       RemoveInputSource(telnetISR);
13161     }
13162     if (icsPR != NoProc) {
13163       DestroyChildProcess(icsPR, TRUE);
13164     }
13165
13166     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13167     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13168
13169     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13170     /* make sure this other one finishes before killing it!                  */
13171     if(endingGame) { int count = 0;
13172         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13173         while(endingGame && count++ < 10) DoSleep(1);
13174         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13175     }
13176
13177     /* Kill off chess programs */
13178     if (first.pr != NoProc) {
13179         ExitAnalyzeMode();
13180
13181         DoSleep( appData.delayBeforeQuit );
13182         SendToProgram("quit\n", &first);
13183         DoSleep( appData.delayAfterQuit );
13184         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13185     }
13186     if (second.pr != NoProc) {
13187         DoSleep( appData.delayBeforeQuit );
13188         SendToProgram("quit\n", &second);
13189         DoSleep( appData.delayAfterQuit );
13190         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13191     }
13192     if (first.isr != NULL) {
13193         RemoveInputSource(first.isr);
13194     }
13195     if (second.isr != NULL) {
13196         RemoveInputSource(second.isr);
13197     }
13198
13199     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13200     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13201
13202     ShutDownFrontEnd();
13203     exit(status);
13204 }
13205
13206 void
13207 PauseEvent ()
13208 {
13209     if (appData.debugMode)
13210         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13211     if (pausing) {
13212         pausing = FALSE;
13213         ModeHighlight();
13214         if (gameMode == MachinePlaysWhite ||
13215             gameMode == MachinePlaysBlack) {
13216             StartClocks();
13217         } else {
13218             DisplayBothClocks();
13219         }
13220         if (gameMode == PlayFromGameFile) {
13221             if (appData.timeDelay >= 0)
13222                 AutoPlayGameLoop();
13223         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13224             Reset(FALSE, TRUE);
13225             SendToICS(ics_prefix);
13226             SendToICS("refresh\n");
13227         } else if (currentMove < forwardMostMove) {
13228             ForwardInner(forwardMostMove);
13229         }
13230         pauseExamInvalid = FALSE;
13231     } else {
13232         switch (gameMode) {
13233           default:
13234             return;
13235           case IcsExamining:
13236             pauseExamForwardMostMove = forwardMostMove;
13237             pauseExamInvalid = FALSE;
13238             /* fall through */
13239           case IcsObserving:
13240           case IcsPlayingWhite:
13241           case IcsPlayingBlack:
13242             pausing = TRUE;
13243             ModeHighlight();
13244             return;
13245           case PlayFromGameFile:
13246             (void) StopLoadGameTimer();
13247             pausing = TRUE;
13248             ModeHighlight();
13249             break;
13250           case BeginningOfGame:
13251             if (appData.icsActive) return;
13252             /* else fall through */
13253           case MachinePlaysWhite:
13254           case MachinePlaysBlack:
13255           case TwoMachinesPlay:
13256             if (forwardMostMove == 0)
13257               return;           /* don't pause if no one has moved */
13258             if ((gameMode == MachinePlaysWhite &&
13259                  !WhiteOnMove(forwardMostMove)) ||
13260                 (gameMode == MachinePlaysBlack &&
13261                  WhiteOnMove(forwardMostMove))) {
13262                 StopClocks();
13263             }
13264             pausing = TRUE;
13265             ModeHighlight();
13266             break;
13267         }
13268     }
13269 }
13270
13271 void
13272 EditCommentEvent ()
13273 {
13274     char title[MSG_SIZ];
13275
13276     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13277       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13278     } else {
13279       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13280                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13281                parseList[currentMove - 1]);
13282     }
13283
13284     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13285 }
13286
13287
13288 void
13289 EditTagsEvent ()
13290 {
13291     char *tags = PGNTags(&gameInfo);
13292     bookUp = FALSE;
13293     EditTagsPopUp(tags, NULL);
13294     free(tags);
13295 }
13296
13297 void
13298 AnalyzeModeEvent ()
13299 {
13300     if (appData.noChessProgram || gameMode == AnalyzeMode)
13301       return;
13302
13303     if (gameMode != AnalyzeFile) {
13304         if (!appData.icsEngineAnalyze) {
13305                EditGameEvent();
13306                if (gameMode != EditGame) return;
13307         }
13308         ResurrectChessProgram();
13309         SendToProgram("analyze\n", &first);
13310         first.analyzing = TRUE;
13311         /*first.maybeThinking = TRUE;*/
13312         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13313         EngineOutputPopUp();
13314     }
13315     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13316     pausing = FALSE;
13317     ModeHighlight();
13318     SetGameInfo();
13319
13320     StartAnalysisClock();
13321     GetTimeMark(&lastNodeCountTime);
13322     lastNodeCount = 0;
13323 }
13324
13325 void
13326 AnalyzeFileEvent ()
13327 {
13328     if (appData.noChessProgram || gameMode == AnalyzeFile)
13329       return;
13330
13331     if (gameMode != AnalyzeMode) {
13332         EditGameEvent();
13333         if (gameMode != EditGame) return;
13334         ResurrectChessProgram();
13335         SendToProgram("analyze\n", &first);
13336         first.analyzing = TRUE;
13337         /*first.maybeThinking = TRUE;*/
13338         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13339         EngineOutputPopUp();
13340     }
13341     gameMode = AnalyzeFile;
13342     pausing = FALSE;
13343     ModeHighlight();
13344     SetGameInfo();
13345
13346     StartAnalysisClock();
13347     GetTimeMark(&lastNodeCountTime);
13348     lastNodeCount = 0;
13349     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13350 }
13351
13352 void
13353 MachineWhiteEvent ()
13354 {
13355     char buf[MSG_SIZ];
13356     char *bookHit = NULL;
13357
13358     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13359       return;
13360
13361
13362     if (gameMode == PlayFromGameFile ||
13363         gameMode == TwoMachinesPlay  ||
13364         gameMode == Training         ||
13365         gameMode == AnalyzeMode      ||
13366         gameMode == EndOfGame)
13367         EditGameEvent();
13368
13369     if (gameMode == EditPosition)
13370         EditPositionDone(TRUE);
13371
13372     if (!WhiteOnMove(currentMove)) {
13373         DisplayError(_("It is not White's turn"), 0);
13374         return;
13375     }
13376
13377     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13378       ExitAnalyzeMode();
13379
13380     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13381         gameMode == AnalyzeFile)
13382         TruncateGame();
13383
13384     ResurrectChessProgram();    /* in case it isn't running */
13385     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13386         gameMode = MachinePlaysWhite;
13387         ResetClocks();
13388     } else
13389     gameMode = MachinePlaysWhite;
13390     pausing = FALSE;
13391     ModeHighlight();
13392     SetGameInfo();
13393     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13394     DisplayTitle(buf);
13395     if (first.sendName) {
13396       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13397       SendToProgram(buf, &first);
13398     }
13399     if (first.sendTime) {
13400       if (first.useColors) {
13401         SendToProgram("black\n", &first); /*gnu kludge*/
13402       }
13403       SendTimeRemaining(&first, TRUE);
13404     }
13405     if (first.useColors) {
13406       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13407     }
13408     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13409     SetMachineThinkingEnables();
13410     first.maybeThinking = TRUE;
13411     StartClocks();
13412     firstMove = FALSE;
13413
13414     if (appData.autoFlipView && !flipView) {
13415       flipView = !flipView;
13416       DrawPosition(FALSE, NULL);
13417       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13418     }
13419
13420     if(bookHit) { // [HGM] book: simulate book reply
13421         static char bookMove[MSG_SIZ]; // a bit generous?
13422
13423         programStats.nodes = programStats.depth = programStats.time =
13424         programStats.score = programStats.got_only_move = 0;
13425         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13426
13427         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13428         strcat(bookMove, bookHit);
13429         HandleMachineMove(bookMove, &first);
13430     }
13431 }
13432
13433 void
13434 MachineBlackEvent ()
13435 {
13436   char buf[MSG_SIZ];
13437   char *bookHit = NULL;
13438
13439     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13440         return;
13441
13442
13443     if (gameMode == PlayFromGameFile ||
13444         gameMode == TwoMachinesPlay  ||
13445         gameMode == Training         ||
13446         gameMode == AnalyzeMode      ||
13447         gameMode == EndOfGame)
13448         EditGameEvent();
13449
13450     if (gameMode == EditPosition)
13451         EditPositionDone(TRUE);
13452
13453     if (WhiteOnMove(currentMove)) {
13454         DisplayError(_("It is not Black's turn"), 0);
13455         return;
13456     }
13457
13458     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13459       ExitAnalyzeMode();
13460
13461     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13462         gameMode == AnalyzeFile)
13463         TruncateGame();
13464
13465     ResurrectChessProgram();    /* in case it isn't running */
13466     gameMode = MachinePlaysBlack;
13467     pausing = FALSE;
13468     ModeHighlight();
13469     SetGameInfo();
13470     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13471     DisplayTitle(buf);
13472     if (first.sendName) {
13473       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13474       SendToProgram(buf, &first);
13475     }
13476     if (first.sendTime) {
13477       if (first.useColors) {
13478         SendToProgram("white\n", &first); /*gnu kludge*/
13479       }
13480       SendTimeRemaining(&first, FALSE);
13481     }
13482     if (first.useColors) {
13483       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13484     }
13485     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13486     SetMachineThinkingEnables();
13487     first.maybeThinking = TRUE;
13488     StartClocks();
13489
13490     if (appData.autoFlipView && flipView) {
13491       flipView = !flipView;
13492       DrawPosition(FALSE, NULL);
13493       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13494     }
13495     if(bookHit) { // [HGM] book: simulate book reply
13496         static char bookMove[MSG_SIZ]; // a bit generous?
13497
13498         programStats.nodes = programStats.depth = programStats.time =
13499         programStats.score = programStats.got_only_move = 0;
13500         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13501
13502         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13503         strcat(bookMove, bookHit);
13504         HandleMachineMove(bookMove, &first);
13505     }
13506 }
13507
13508
13509 void
13510 DisplayTwoMachinesTitle ()
13511 {
13512     char buf[MSG_SIZ];
13513     if (appData.matchGames > 0) {
13514         if(appData.tourneyFile[0]) {
13515           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13516                    gameInfo.white, _("vs."), gameInfo.black,
13517                    nextGame+1, appData.matchGames+1,
13518                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13519         } else 
13520         if (first.twoMachinesColor[0] == 'w') {
13521           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13522                    gameInfo.white, _("vs."),  gameInfo.black,
13523                    first.matchWins, second.matchWins,
13524                    matchGame - 1 - (first.matchWins + second.matchWins));
13525         } else {
13526           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13527                    gameInfo.white, _("vs."), gameInfo.black,
13528                    second.matchWins, first.matchWins,
13529                    matchGame - 1 - (first.matchWins + second.matchWins));
13530         }
13531     } else {
13532       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13533     }
13534     DisplayTitle(buf);
13535 }
13536
13537 void
13538 SettingsMenuIfReady ()
13539 {
13540   if (second.lastPing != second.lastPong) {
13541     DisplayMessage("", _("Waiting for second chess program"));
13542     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13543     return;
13544   }
13545   ThawUI();
13546   DisplayMessage("", "");
13547   SettingsPopUp(&second);
13548 }
13549
13550 int
13551 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13552 {
13553     char buf[MSG_SIZ];
13554     if (cps->pr == NoProc) {
13555         StartChessProgram(cps);
13556         if (cps->protocolVersion == 1) {
13557           retry();
13558         } else {
13559           /* kludge: allow timeout for initial "feature" command */
13560           FreezeUI();
13561           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13562           DisplayMessage("", buf);
13563           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13564         }
13565         return 1;
13566     }
13567     return 0;
13568 }
13569
13570 void
13571 TwoMachinesEvent P((void))
13572 {
13573     int i;
13574     char buf[MSG_SIZ];
13575     ChessProgramState *onmove;
13576     char *bookHit = NULL;
13577     static int stalling = 0;
13578     TimeMark now;
13579     long wait;
13580
13581     if (appData.noChessProgram) return;
13582
13583     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13584         DisplayError("second engine does not play this", 0);
13585         return;
13586     }
13587
13588     switch (gameMode) {
13589       case TwoMachinesPlay:
13590         return;
13591       case MachinePlaysWhite:
13592       case MachinePlaysBlack:
13593         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13594             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13595             return;
13596         }
13597         /* fall through */
13598       case BeginningOfGame:
13599       case PlayFromGameFile:
13600       case EndOfGame:
13601         EditGameEvent();
13602         if (gameMode != EditGame) return;
13603         break;
13604       case EditPosition:
13605         EditPositionDone(TRUE);
13606         break;
13607       case AnalyzeMode:
13608       case AnalyzeFile:
13609         ExitAnalyzeMode();
13610         break;
13611       case EditGame:
13612       default:
13613         break;
13614     }
13615
13616 //    forwardMostMove = currentMove;
13617     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13618
13619     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13620
13621     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13622     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13623       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13624       return;
13625     }
13626     if(!stalling) {
13627       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13628       SendToProgram("force\n", &second);
13629       stalling = 1;
13630       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13631       return;
13632     }
13633     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13634     if(appData.matchPause>10000 || appData.matchPause<10)
13635                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13636     wait = SubtractTimeMarks(&now, &pauseStart);
13637     if(wait < appData.matchPause) {
13638         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13639         return;
13640     }
13641     // we are now committed to starting the game
13642     stalling = 0;
13643     DisplayMessage("", "");
13644     if (startedFromSetupPosition) {
13645         SendBoard(&second, backwardMostMove);
13646     if (appData.debugMode) {
13647         fprintf(debugFP, "Two Machines\n");
13648     }
13649     }
13650     for (i = backwardMostMove; i < forwardMostMove; i++) {
13651         SendMoveToProgram(i, &second);
13652     }
13653
13654     gameMode = TwoMachinesPlay;
13655     pausing = FALSE;
13656     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13657     SetGameInfo();
13658     DisplayTwoMachinesTitle();
13659     firstMove = TRUE;
13660     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13661         onmove = &first;
13662     } else {
13663         onmove = &second;
13664     }
13665     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13666     SendToProgram(first.computerString, &first);
13667     if (first.sendName) {
13668       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13669       SendToProgram(buf, &first);
13670     }
13671     SendToProgram(second.computerString, &second);
13672     if (second.sendName) {
13673       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13674       SendToProgram(buf, &second);
13675     }
13676
13677     ResetClocks();
13678     if (!first.sendTime || !second.sendTime) {
13679         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13680         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13681     }
13682     if (onmove->sendTime) {
13683       if (onmove->useColors) {
13684         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13685       }
13686       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13687     }
13688     if (onmove->useColors) {
13689       SendToProgram(onmove->twoMachinesColor, onmove);
13690     }
13691     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13692 //    SendToProgram("go\n", onmove);
13693     onmove->maybeThinking = TRUE;
13694     SetMachineThinkingEnables();
13695
13696     StartClocks();
13697
13698     if(bookHit) { // [HGM] book: simulate book reply
13699         static char bookMove[MSG_SIZ]; // a bit generous?
13700
13701         programStats.nodes = programStats.depth = programStats.time =
13702         programStats.score = programStats.got_only_move = 0;
13703         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13704
13705         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13706         strcat(bookMove, bookHit);
13707         savedMessage = bookMove; // args for deferred call
13708         savedState = onmove;
13709         ScheduleDelayedEvent(DeferredBookMove, 1);
13710     }
13711 }
13712
13713 void
13714 TrainingEvent ()
13715 {
13716     if (gameMode == Training) {
13717       SetTrainingModeOff();
13718       gameMode = PlayFromGameFile;
13719       DisplayMessage("", _("Training mode off"));
13720     } else {
13721       gameMode = Training;
13722       animateTraining = appData.animate;
13723
13724       /* make sure we are not already at the end of the game */
13725       if (currentMove < forwardMostMove) {
13726         SetTrainingModeOn();
13727         DisplayMessage("", _("Training mode on"));
13728       } else {
13729         gameMode = PlayFromGameFile;
13730         DisplayError(_("Already at end of game"), 0);
13731       }
13732     }
13733     ModeHighlight();
13734 }
13735
13736 void
13737 IcsClientEvent ()
13738 {
13739     if (!appData.icsActive) return;
13740     switch (gameMode) {
13741       case IcsPlayingWhite:
13742       case IcsPlayingBlack:
13743       case IcsObserving:
13744       case IcsIdle:
13745       case BeginningOfGame:
13746       case IcsExamining:
13747         return;
13748
13749       case EditGame:
13750         break;
13751
13752       case EditPosition:
13753         EditPositionDone(TRUE);
13754         break;
13755
13756       case AnalyzeMode:
13757       case AnalyzeFile:
13758         ExitAnalyzeMode();
13759         break;
13760
13761       default:
13762         EditGameEvent();
13763         break;
13764     }
13765
13766     gameMode = IcsIdle;
13767     ModeHighlight();
13768     return;
13769 }
13770
13771 void
13772 EditGameEvent ()
13773 {
13774     int i;
13775
13776     switch (gameMode) {
13777       case Training:
13778         SetTrainingModeOff();
13779         break;
13780       case MachinePlaysWhite:
13781       case MachinePlaysBlack:
13782       case BeginningOfGame:
13783         SendToProgram("force\n", &first);
13784         SetUserThinkingEnables();
13785         break;
13786       case PlayFromGameFile:
13787         (void) StopLoadGameTimer();
13788         if (gameFileFP != NULL) {
13789             gameFileFP = NULL;
13790         }
13791         break;
13792       case EditPosition:
13793         EditPositionDone(TRUE);
13794         break;
13795       case AnalyzeMode:
13796       case AnalyzeFile:
13797         ExitAnalyzeMode();
13798         SendToProgram("force\n", &first);
13799         break;
13800       case TwoMachinesPlay:
13801         GameEnds(EndOfFile, NULL, GE_PLAYER);
13802         ResurrectChessProgram();
13803         SetUserThinkingEnables();
13804         break;
13805       case EndOfGame:
13806         ResurrectChessProgram();
13807         break;
13808       case IcsPlayingBlack:
13809       case IcsPlayingWhite:
13810         DisplayError(_("Warning: You are still playing a game"), 0);
13811         break;
13812       case IcsObserving:
13813         DisplayError(_("Warning: You are still observing a game"), 0);
13814         break;
13815       case IcsExamining:
13816         DisplayError(_("Warning: You are still examining a game"), 0);
13817         break;
13818       case IcsIdle:
13819         break;
13820       case EditGame:
13821       default:
13822         return;
13823     }
13824
13825     pausing = FALSE;
13826     StopClocks();
13827     first.offeredDraw = second.offeredDraw = 0;
13828
13829     if (gameMode == PlayFromGameFile) {
13830         whiteTimeRemaining = timeRemaining[0][currentMove];
13831         blackTimeRemaining = timeRemaining[1][currentMove];
13832         DisplayTitle("");
13833     }
13834
13835     if (gameMode == MachinePlaysWhite ||
13836         gameMode == MachinePlaysBlack ||
13837         gameMode == TwoMachinesPlay ||
13838         gameMode == EndOfGame) {
13839         i = forwardMostMove;
13840         while (i > currentMove) {
13841             SendToProgram("undo\n", &first);
13842             i--;
13843         }
13844         if(!adjustedClock) {
13845         whiteTimeRemaining = timeRemaining[0][currentMove];
13846         blackTimeRemaining = timeRemaining[1][currentMove];
13847         DisplayBothClocks();
13848         }
13849         if (whiteFlag || blackFlag) {
13850             whiteFlag = blackFlag = 0;
13851         }
13852         DisplayTitle("");
13853     }
13854
13855     gameMode = EditGame;
13856     ModeHighlight();
13857     SetGameInfo();
13858 }
13859
13860
13861 void
13862 EditPositionEvent ()
13863 {
13864     if (gameMode == EditPosition) {
13865         EditGameEvent();
13866         return;
13867     }
13868
13869     EditGameEvent();
13870     if (gameMode != EditGame) return;
13871
13872     gameMode = EditPosition;
13873     ModeHighlight();
13874     SetGameInfo();
13875     if (currentMove > 0)
13876       CopyBoard(boards[0], boards[currentMove]);
13877
13878     blackPlaysFirst = !WhiteOnMove(currentMove);
13879     ResetClocks();
13880     currentMove = forwardMostMove = backwardMostMove = 0;
13881     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13882     DisplayMove(-1);
13883     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13884 }
13885
13886 void
13887 ExitAnalyzeMode ()
13888 {
13889     /* [DM] icsEngineAnalyze - possible call from other functions */
13890     if (appData.icsEngineAnalyze) {
13891         appData.icsEngineAnalyze = FALSE;
13892
13893         DisplayMessage("",_("Close ICS engine analyze..."));
13894     }
13895     if (first.analysisSupport && first.analyzing) {
13896       SendToProgram("exit\n", &first);
13897       first.analyzing = FALSE;
13898     }
13899     thinkOutput[0] = NULLCHAR;
13900 }
13901
13902 void
13903 EditPositionDone (Boolean fakeRights)
13904 {
13905     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13906
13907     startedFromSetupPosition = TRUE;
13908     InitChessProgram(&first, FALSE);
13909     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13910       boards[0][EP_STATUS] = EP_NONE;
13911       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13912     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13913         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13914         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13915       } else boards[0][CASTLING][2] = NoRights;
13916     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13917         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13918         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13919       } else boards[0][CASTLING][5] = NoRights;
13920     }
13921     SendToProgram("force\n", &first);
13922     if (blackPlaysFirst) {
13923         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13924         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13925         currentMove = forwardMostMove = backwardMostMove = 1;
13926         CopyBoard(boards[1], boards[0]);
13927     } else {
13928         currentMove = forwardMostMove = backwardMostMove = 0;
13929     }
13930     SendBoard(&first, forwardMostMove);
13931     if (appData.debugMode) {
13932         fprintf(debugFP, "EditPosDone\n");
13933     }
13934     DisplayTitle("");
13935     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13936     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13937     gameMode = EditGame;
13938     ModeHighlight();
13939     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13940     ClearHighlights(); /* [AS] */
13941 }
13942
13943 /* Pause for `ms' milliseconds */
13944 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13945 void
13946 TimeDelay (long ms)
13947 {
13948     TimeMark m1, m2;
13949
13950     GetTimeMark(&m1);
13951     do {
13952         GetTimeMark(&m2);
13953     } while (SubtractTimeMarks(&m2, &m1) < ms);
13954 }
13955
13956 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13957 void
13958 SendMultiLineToICS (char *buf)
13959 {
13960     char temp[MSG_SIZ+1], *p;
13961     int len;
13962
13963     len = strlen(buf);
13964     if (len > MSG_SIZ)
13965       len = MSG_SIZ;
13966
13967     strncpy(temp, buf, len);
13968     temp[len] = 0;
13969
13970     p = temp;
13971     while (*p) {
13972         if (*p == '\n' || *p == '\r')
13973           *p = ' ';
13974         ++p;
13975     }
13976
13977     strcat(temp, "\n");
13978     SendToICS(temp);
13979     SendToPlayer(temp, strlen(temp));
13980 }
13981
13982 void
13983 SetWhiteToPlayEvent ()
13984 {
13985     if (gameMode == EditPosition) {
13986         blackPlaysFirst = FALSE;
13987         DisplayBothClocks();    /* works because currentMove is 0 */
13988     } else if (gameMode == IcsExamining) {
13989         SendToICS(ics_prefix);
13990         SendToICS("tomove white\n");
13991     }
13992 }
13993
13994 void
13995 SetBlackToPlayEvent ()
13996 {
13997     if (gameMode == EditPosition) {
13998         blackPlaysFirst = TRUE;
13999         currentMove = 1;        /* kludge */
14000         DisplayBothClocks();
14001         currentMove = 0;
14002     } else if (gameMode == IcsExamining) {
14003         SendToICS(ics_prefix);
14004         SendToICS("tomove black\n");
14005     }
14006 }
14007
14008 void
14009 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14010 {
14011     char buf[MSG_SIZ];
14012     ChessSquare piece = boards[0][y][x];
14013
14014     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14015
14016     switch (selection) {
14017       case ClearBoard:
14018         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14019             SendToICS(ics_prefix);
14020             SendToICS("bsetup clear\n");
14021         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14022             SendToICS(ics_prefix);
14023             SendToICS("clearboard\n");
14024         } else {
14025             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14026                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14027                 for (y = 0; y < BOARD_HEIGHT; y++) {
14028                     if (gameMode == IcsExamining) {
14029                         if (boards[currentMove][y][x] != EmptySquare) {
14030                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14031                                     AAA + x, ONE + y);
14032                             SendToICS(buf);
14033                         }
14034                     } else {
14035                         boards[0][y][x] = p;
14036                     }
14037                 }
14038             }
14039         }
14040         if (gameMode == EditPosition) {
14041             DrawPosition(FALSE, boards[0]);
14042         }
14043         break;
14044
14045       case WhitePlay:
14046         SetWhiteToPlayEvent();
14047         break;
14048
14049       case BlackPlay:
14050         SetBlackToPlayEvent();
14051         break;
14052
14053       case EmptySquare:
14054         if (gameMode == IcsExamining) {
14055             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14056             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14057             SendToICS(buf);
14058         } else {
14059             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14060                 if(x == BOARD_LEFT-2) {
14061                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14062                     boards[0][y][1] = 0;
14063                 } else
14064                 if(x == BOARD_RGHT+1) {
14065                     if(y >= gameInfo.holdingsSize) break;
14066                     boards[0][y][BOARD_WIDTH-2] = 0;
14067                 } else break;
14068             }
14069             boards[0][y][x] = EmptySquare;
14070             DrawPosition(FALSE, boards[0]);
14071         }
14072         break;
14073
14074       case PromotePiece:
14075         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14076            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14077             selection = (ChessSquare) (PROMOTED piece);
14078         } else if(piece == EmptySquare) selection = WhiteSilver;
14079         else selection = (ChessSquare)((int)piece - 1);
14080         goto defaultlabel;
14081
14082       case DemotePiece:
14083         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14084            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14085             selection = (ChessSquare) (DEMOTED piece);
14086         } else if(piece == EmptySquare) selection = BlackSilver;
14087         else selection = (ChessSquare)((int)piece + 1);
14088         goto defaultlabel;
14089
14090       case WhiteQueen:
14091       case BlackQueen:
14092         if(gameInfo.variant == VariantShatranj ||
14093            gameInfo.variant == VariantXiangqi  ||
14094            gameInfo.variant == VariantCourier  ||
14095            gameInfo.variant == VariantMakruk     )
14096             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14097         goto defaultlabel;
14098
14099       case WhiteKing:
14100       case BlackKing:
14101         if(gameInfo.variant == VariantXiangqi)
14102             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14103         if(gameInfo.variant == VariantKnightmate)
14104             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14105       default:
14106         defaultlabel:
14107         if (gameMode == IcsExamining) {
14108             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14109             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14110                      PieceToChar(selection), AAA + x, ONE + y);
14111             SendToICS(buf);
14112         } else {
14113             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14114                 int n;
14115                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14116                     n = PieceToNumber(selection - BlackPawn);
14117                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14118                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14119                     boards[0][BOARD_HEIGHT-1-n][1]++;
14120                 } else
14121                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14122                     n = PieceToNumber(selection);
14123                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14124                     boards[0][n][BOARD_WIDTH-1] = selection;
14125                     boards[0][n][BOARD_WIDTH-2]++;
14126                 }
14127             } else
14128             boards[0][y][x] = selection;
14129             DrawPosition(TRUE, boards[0]);
14130             ClearHighlights();
14131             fromX = fromY = -1;
14132         }
14133         break;
14134     }
14135 }
14136
14137
14138 void
14139 DropMenuEvent (ChessSquare selection, int x, int y)
14140 {
14141     ChessMove moveType;
14142
14143     switch (gameMode) {
14144       case IcsPlayingWhite:
14145       case MachinePlaysBlack:
14146         if (!WhiteOnMove(currentMove)) {
14147             DisplayMoveError(_("It is Black's turn"));
14148             return;
14149         }
14150         moveType = WhiteDrop;
14151         break;
14152       case IcsPlayingBlack:
14153       case MachinePlaysWhite:
14154         if (WhiteOnMove(currentMove)) {
14155             DisplayMoveError(_("It is White's turn"));
14156             return;
14157         }
14158         moveType = BlackDrop;
14159         break;
14160       case EditGame:
14161         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14162         break;
14163       default:
14164         return;
14165     }
14166
14167     if (moveType == BlackDrop && selection < BlackPawn) {
14168       selection = (ChessSquare) ((int) selection
14169                                  + (int) BlackPawn - (int) WhitePawn);
14170     }
14171     if (boards[currentMove][y][x] != EmptySquare) {
14172         DisplayMoveError(_("That square is occupied"));
14173         return;
14174     }
14175
14176     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14177 }
14178
14179 void
14180 AcceptEvent ()
14181 {
14182     /* Accept a pending offer of any kind from opponent */
14183
14184     if (appData.icsActive) {
14185         SendToICS(ics_prefix);
14186         SendToICS("accept\n");
14187     } else if (cmailMsgLoaded) {
14188         if (currentMove == cmailOldMove &&
14189             commentList[cmailOldMove] != NULL &&
14190             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14191                    "Black offers a draw" : "White offers a draw")) {
14192             TruncateGame();
14193             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14194             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14195         } else {
14196             DisplayError(_("There is no pending offer on this move"), 0);
14197             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14198         }
14199     } else {
14200         /* Not used for offers from chess program */
14201     }
14202 }
14203
14204 void
14205 DeclineEvent ()
14206 {
14207     /* Decline a pending offer of any kind from opponent */
14208
14209     if (appData.icsActive) {
14210         SendToICS(ics_prefix);
14211         SendToICS("decline\n");
14212     } else if (cmailMsgLoaded) {
14213         if (currentMove == cmailOldMove &&
14214             commentList[cmailOldMove] != NULL &&
14215             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14216                    "Black offers a draw" : "White offers a draw")) {
14217 #ifdef NOTDEF
14218             AppendComment(cmailOldMove, "Draw declined", TRUE);
14219             DisplayComment(cmailOldMove - 1, "Draw declined");
14220 #endif /*NOTDEF*/
14221         } else {
14222             DisplayError(_("There is no pending offer on this move"), 0);
14223         }
14224     } else {
14225         /* Not used for offers from chess program */
14226     }
14227 }
14228
14229 void
14230 RematchEvent ()
14231 {
14232     /* Issue ICS rematch command */
14233     if (appData.icsActive) {
14234         SendToICS(ics_prefix);
14235         SendToICS("rematch\n");
14236     }
14237 }
14238
14239 void
14240 CallFlagEvent ()
14241 {
14242     /* Call your opponent's flag (claim a win on time) */
14243     if (appData.icsActive) {
14244         SendToICS(ics_prefix);
14245         SendToICS("flag\n");
14246     } else {
14247         switch (gameMode) {
14248           default:
14249             return;
14250           case MachinePlaysWhite:
14251             if (whiteFlag) {
14252                 if (blackFlag)
14253                   GameEnds(GameIsDrawn, "Both players ran out of time",
14254                            GE_PLAYER);
14255                 else
14256                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14257             } else {
14258                 DisplayError(_("Your opponent is not out of time"), 0);
14259             }
14260             break;
14261           case MachinePlaysBlack:
14262             if (blackFlag) {
14263                 if (whiteFlag)
14264                   GameEnds(GameIsDrawn, "Both players ran out of time",
14265                            GE_PLAYER);
14266                 else
14267                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14268             } else {
14269                 DisplayError(_("Your opponent is not out of time"), 0);
14270             }
14271             break;
14272         }
14273     }
14274 }
14275
14276 void
14277 ClockClick (int which)
14278 {       // [HGM] code moved to back-end from winboard.c
14279         if(which) { // black clock
14280           if (gameMode == EditPosition || gameMode == IcsExamining) {
14281             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14282             SetBlackToPlayEvent();
14283           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14284           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14285           } else if (shiftKey) {
14286             AdjustClock(which, -1);
14287           } else if (gameMode == IcsPlayingWhite ||
14288                      gameMode == MachinePlaysBlack) {
14289             CallFlagEvent();
14290           }
14291         } else { // white clock
14292           if (gameMode == EditPosition || gameMode == IcsExamining) {
14293             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14294             SetWhiteToPlayEvent();
14295           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14296           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14297           } else if (shiftKey) {
14298             AdjustClock(which, -1);
14299           } else if (gameMode == IcsPlayingBlack ||
14300                    gameMode == MachinePlaysWhite) {
14301             CallFlagEvent();
14302           }
14303         }
14304 }
14305
14306 void
14307 DrawEvent ()
14308 {
14309     /* Offer draw or accept pending draw offer from opponent */
14310
14311     if (appData.icsActive) {
14312         /* Note: tournament rules require draw offers to be
14313            made after you make your move but before you punch
14314            your clock.  Currently ICS doesn't let you do that;
14315            instead, you immediately punch your clock after making
14316            a move, but you can offer a draw at any time. */
14317
14318         SendToICS(ics_prefix);
14319         SendToICS("draw\n");
14320         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14321     } else if (cmailMsgLoaded) {
14322         if (currentMove == cmailOldMove &&
14323             commentList[cmailOldMove] != NULL &&
14324             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14325                    "Black offers a draw" : "White offers a draw")) {
14326             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14327             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14328         } else if (currentMove == cmailOldMove + 1) {
14329             char *offer = WhiteOnMove(cmailOldMove) ?
14330               "White offers a draw" : "Black offers a draw";
14331             AppendComment(currentMove, offer, TRUE);
14332             DisplayComment(currentMove - 1, offer);
14333             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14334         } else {
14335             DisplayError(_("You must make your move before offering a draw"), 0);
14336             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14337         }
14338     } else if (first.offeredDraw) {
14339         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14340     } else {
14341         if (first.sendDrawOffers) {
14342             SendToProgram("draw\n", &first);
14343             userOfferedDraw = TRUE;
14344         }
14345     }
14346 }
14347
14348 void
14349 AdjournEvent ()
14350 {
14351     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14352
14353     if (appData.icsActive) {
14354         SendToICS(ics_prefix);
14355         SendToICS("adjourn\n");
14356     } else {
14357         /* Currently GNU Chess doesn't offer or accept Adjourns */
14358     }
14359 }
14360
14361
14362 void
14363 AbortEvent ()
14364 {
14365     /* Offer Abort or accept pending Abort offer from opponent */
14366
14367     if (appData.icsActive) {
14368         SendToICS(ics_prefix);
14369         SendToICS("abort\n");
14370     } else {
14371         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14372     }
14373 }
14374
14375 void
14376 ResignEvent ()
14377 {
14378     /* Resign.  You can do this even if it's not your turn. */
14379
14380     if (appData.icsActive) {
14381         SendToICS(ics_prefix);
14382         SendToICS("resign\n");
14383     } else {
14384         switch (gameMode) {
14385           case MachinePlaysWhite:
14386             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14387             break;
14388           case MachinePlaysBlack:
14389             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14390             break;
14391           case EditGame:
14392             if (cmailMsgLoaded) {
14393                 TruncateGame();
14394                 if (WhiteOnMove(cmailOldMove)) {
14395                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14396                 } else {
14397                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14398                 }
14399                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14400             }
14401             break;
14402           default:
14403             break;
14404         }
14405     }
14406 }
14407
14408
14409 void
14410 StopObservingEvent ()
14411 {
14412     /* Stop observing current games */
14413     SendToICS(ics_prefix);
14414     SendToICS("unobserve\n");
14415 }
14416
14417 void
14418 StopExaminingEvent ()
14419 {
14420     /* Stop observing current game */
14421     SendToICS(ics_prefix);
14422     SendToICS("unexamine\n");
14423 }
14424
14425 void
14426 ForwardInner (int target)
14427 {
14428     int limit; int oldSeekGraphUp = seekGraphUp;
14429
14430     if (appData.debugMode)
14431         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14432                 target, currentMove, forwardMostMove);
14433
14434     if (gameMode == EditPosition)
14435       return;
14436
14437     seekGraphUp = FALSE;
14438     MarkTargetSquares(1);
14439
14440     if (gameMode == PlayFromGameFile && !pausing)
14441       PauseEvent();
14442
14443     if (gameMode == IcsExamining && pausing)
14444       limit = pauseExamForwardMostMove;
14445     else
14446       limit = forwardMostMove;
14447
14448     if (target > limit) target = limit;
14449
14450     if (target > 0 && moveList[target - 1][0]) {
14451         int fromX, fromY, toX, toY;
14452         toX = moveList[target - 1][2] - AAA;
14453         toY = moveList[target - 1][3] - ONE;
14454         if (moveList[target - 1][1] == '@') {
14455             if (appData.highlightLastMove) {
14456                 SetHighlights(-1, -1, toX, toY);
14457             }
14458         } else {
14459             fromX = moveList[target - 1][0] - AAA;
14460             fromY = moveList[target - 1][1] - ONE;
14461             if (target == currentMove + 1) {
14462                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14463             }
14464             if (appData.highlightLastMove) {
14465                 SetHighlights(fromX, fromY, toX, toY);
14466             }
14467         }
14468     }
14469     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14470         gameMode == Training || gameMode == PlayFromGameFile ||
14471         gameMode == AnalyzeFile) {
14472         while (currentMove < target) {
14473             SendMoveToProgram(currentMove++, &first);
14474         }
14475     } else {
14476         currentMove = target;
14477     }
14478
14479     if (gameMode == EditGame || gameMode == EndOfGame) {
14480         whiteTimeRemaining = timeRemaining[0][currentMove];
14481         blackTimeRemaining = timeRemaining[1][currentMove];
14482     }
14483     DisplayBothClocks();
14484     DisplayMove(currentMove - 1);
14485     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14486     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14487     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14488         DisplayComment(currentMove - 1, commentList[currentMove]);
14489     }
14490     ClearMap(); // [HGM] exclude: invalidate map
14491 }
14492
14493
14494 void
14495 ForwardEvent ()
14496 {
14497     if (gameMode == IcsExamining && !pausing) {
14498         SendToICS(ics_prefix);
14499         SendToICS("forward\n");
14500     } else {
14501         ForwardInner(currentMove + 1);
14502     }
14503 }
14504
14505 void
14506 ToEndEvent ()
14507 {
14508     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14509         /* to optimze, we temporarily turn off analysis mode while we feed
14510          * the remaining moves to the engine. Otherwise we get analysis output
14511          * after each move.
14512          */
14513         if (first.analysisSupport) {
14514           SendToProgram("exit\nforce\n", &first);
14515           first.analyzing = FALSE;
14516         }
14517     }
14518
14519     if (gameMode == IcsExamining && !pausing) {
14520         SendToICS(ics_prefix);
14521         SendToICS("forward 999999\n");
14522     } else {
14523         ForwardInner(forwardMostMove);
14524     }
14525
14526     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14527         /* we have fed all the moves, so reactivate analysis mode */
14528         SendToProgram("analyze\n", &first);
14529         first.analyzing = TRUE;
14530         /*first.maybeThinking = TRUE;*/
14531         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14532     }
14533 }
14534
14535 void
14536 BackwardInner (int target)
14537 {
14538     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14539
14540     if (appData.debugMode)
14541         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14542                 target, currentMove, forwardMostMove);
14543
14544     if (gameMode == EditPosition) return;
14545     seekGraphUp = FALSE;
14546     MarkTargetSquares(1);
14547     if (currentMove <= backwardMostMove) {
14548         ClearHighlights();
14549         DrawPosition(full_redraw, boards[currentMove]);
14550         return;
14551     }
14552     if (gameMode == PlayFromGameFile && !pausing)
14553       PauseEvent();
14554
14555     if (moveList[target][0]) {
14556         int fromX, fromY, toX, toY;
14557         toX = moveList[target][2] - AAA;
14558         toY = moveList[target][3] - ONE;
14559         if (moveList[target][1] == '@') {
14560             if (appData.highlightLastMove) {
14561                 SetHighlights(-1, -1, toX, toY);
14562             }
14563         } else {
14564             fromX = moveList[target][0] - AAA;
14565             fromY = moveList[target][1] - ONE;
14566             if (target == currentMove - 1) {
14567                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14568             }
14569             if (appData.highlightLastMove) {
14570                 SetHighlights(fromX, fromY, toX, toY);
14571             }
14572         }
14573     }
14574     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14575         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14576         while (currentMove > target) {
14577             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14578                 // null move cannot be undone. Reload program with move history before it.
14579                 int i;
14580                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14581                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14582                 }
14583                 SendBoard(&first, i); 
14584                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14585                 break;
14586             }
14587             SendToProgram("undo\n", &first);
14588             currentMove--;
14589         }
14590     } else {
14591         currentMove = target;
14592     }
14593
14594     if (gameMode == EditGame || gameMode == EndOfGame) {
14595         whiteTimeRemaining = timeRemaining[0][currentMove];
14596         blackTimeRemaining = timeRemaining[1][currentMove];
14597     }
14598     DisplayBothClocks();
14599     DisplayMove(currentMove - 1);
14600     DrawPosition(full_redraw, boards[currentMove]);
14601     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14602     // [HGM] PV info: routine tests if comment empty
14603     DisplayComment(currentMove - 1, commentList[currentMove]);
14604     ClearMap(); // [HGM] exclude: invalidate map
14605 }
14606
14607 void
14608 BackwardEvent ()
14609 {
14610     if (gameMode == IcsExamining && !pausing) {
14611         SendToICS(ics_prefix);
14612         SendToICS("backward\n");
14613     } else {
14614         BackwardInner(currentMove - 1);
14615     }
14616 }
14617
14618 void
14619 ToStartEvent ()
14620 {
14621     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14622         /* to optimize, we temporarily turn off analysis mode while we undo
14623          * all the moves. Otherwise we get analysis output after each undo.
14624          */
14625         if (first.analysisSupport) {
14626           SendToProgram("exit\nforce\n", &first);
14627           first.analyzing = FALSE;
14628         }
14629     }
14630
14631     if (gameMode == IcsExamining && !pausing) {
14632         SendToICS(ics_prefix);
14633         SendToICS("backward 999999\n");
14634     } else {
14635         BackwardInner(backwardMostMove);
14636     }
14637
14638     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14639         /* we have fed all the moves, so reactivate analysis mode */
14640         SendToProgram("analyze\n", &first);
14641         first.analyzing = TRUE;
14642         /*first.maybeThinking = TRUE;*/
14643         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14644     }
14645 }
14646
14647 void
14648 ToNrEvent (int to)
14649 {
14650   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14651   if (to >= forwardMostMove) to = forwardMostMove;
14652   if (to <= backwardMostMove) to = backwardMostMove;
14653   if (to < currentMove) {
14654     BackwardInner(to);
14655   } else {
14656     ForwardInner(to);
14657   }
14658 }
14659
14660 void
14661 RevertEvent (Boolean annotate)
14662 {
14663     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14664         return;
14665     }
14666     if (gameMode != IcsExamining) {
14667         DisplayError(_("You are not examining a game"), 0);
14668         return;
14669     }
14670     if (pausing) {
14671         DisplayError(_("You can't revert while pausing"), 0);
14672         return;
14673     }
14674     SendToICS(ics_prefix);
14675     SendToICS("revert\n");
14676 }
14677
14678 void
14679 RetractMoveEvent ()
14680 {
14681     switch (gameMode) {
14682       case MachinePlaysWhite:
14683       case MachinePlaysBlack:
14684         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14685             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14686             return;
14687         }
14688         if (forwardMostMove < 2) return;
14689         currentMove = forwardMostMove = forwardMostMove - 2;
14690         whiteTimeRemaining = timeRemaining[0][currentMove];
14691         blackTimeRemaining = timeRemaining[1][currentMove];
14692         DisplayBothClocks();
14693         DisplayMove(currentMove - 1);
14694         ClearHighlights();/*!! could figure this out*/
14695         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14696         SendToProgram("remove\n", &first);
14697         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14698         break;
14699
14700       case BeginningOfGame:
14701       default:
14702         break;
14703
14704       case IcsPlayingWhite:
14705       case IcsPlayingBlack:
14706         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14707             SendToICS(ics_prefix);
14708             SendToICS("takeback 2\n");
14709         } else {
14710             SendToICS(ics_prefix);
14711             SendToICS("takeback 1\n");
14712         }
14713         break;
14714     }
14715 }
14716
14717 void
14718 MoveNowEvent ()
14719 {
14720     ChessProgramState *cps;
14721
14722     switch (gameMode) {
14723       case MachinePlaysWhite:
14724         if (!WhiteOnMove(forwardMostMove)) {
14725             DisplayError(_("It is your turn"), 0);
14726             return;
14727         }
14728         cps = &first;
14729         break;
14730       case MachinePlaysBlack:
14731         if (WhiteOnMove(forwardMostMove)) {
14732             DisplayError(_("It is your turn"), 0);
14733             return;
14734         }
14735         cps = &first;
14736         break;
14737       case TwoMachinesPlay:
14738         if (WhiteOnMove(forwardMostMove) ==
14739             (first.twoMachinesColor[0] == 'w')) {
14740             cps = &first;
14741         } else {
14742             cps = &second;
14743         }
14744         break;
14745       case BeginningOfGame:
14746       default:
14747         return;
14748     }
14749     SendToProgram("?\n", cps);
14750 }
14751
14752 void
14753 TruncateGameEvent ()
14754 {
14755     EditGameEvent();
14756     if (gameMode != EditGame) return;
14757     TruncateGame();
14758 }
14759
14760 void
14761 TruncateGame ()
14762 {
14763     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14764     if (forwardMostMove > currentMove) {
14765         if (gameInfo.resultDetails != NULL) {
14766             free(gameInfo.resultDetails);
14767             gameInfo.resultDetails = NULL;
14768             gameInfo.result = GameUnfinished;
14769         }
14770         forwardMostMove = currentMove;
14771         HistorySet(parseList, backwardMostMove, forwardMostMove,
14772                    currentMove-1);
14773     }
14774 }
14775
14776 void
14777 HintEvent ()
14778 {
14779     if (appData.noChessProgram) return;
14780     switch (gameMode) {
14781       case MachinePlaysWhite:
14782         if (WhiteOnMove(forwardMostMove)) {
14783             DisplayError(_("Wait until your turn"), 0);
14784             return;
14785         }
14786         break;
14787       case BeginningOfGame:
14788       case MachinePlaysBlack:
14789         if (!WhiteOnMove(forwardMostMove)) {
14790             DisplayError(_("Wait until your turn"), 0);
14791             return;
14792         }
14793         break;
14794       default:
14795         DisplayError(_("No hint available"), 0);
14796         return;
14797     }
14798     SendToProgram("hint\n", &first);
14799     hintRequested = TRUE;
14800 }
14801
14802 void
14803 BookEvent ()
14804 {
14805     if (appData.noChessProgram) return;
14806     switch (gameMode) {
14807       case MachinePlaysWhite:
14808         if (WhiteOnMove(forwardMostMove)) {
14809             DisplayError(_("Wait until your turn"), 0);
14810             return;
14811         }
14812         break;
14813       case BeginningOfGame:
14814       case MachinePlaysBlack:
14815         if (!WhiteOnMove(forwardMostMove)) {
14816             DisplayError(_("Wait until your turn"), 0);
14817             return;
14818         }
14819         break;
14820       case EditPosition:
14821         EditPositionDone(TRUE);
14822         break;
14823       case TwoMachinesPlay:
14824         return;
14825       default:
14826         break;
14827     }
14828     SendToProgram("bk\n", &first);
14829     bookOutput[0] = NULLCHAR;
14830     bookRequested = TRUE;
14831 }
14832
14833 void
14834 AboutGameEvent ()
14835 {
14836     char *tags = PGNTags(&gameInfo);
14837     TagsPopUp(tags, CmailMsg());
14838     free(tags);
14839 }
14840
14841 /* end button procedures */
14842
14843 void
14844 PrintPosition (FILE *fp, int move)
14845 {
14846     int i, j;
14847
14848     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14849         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14850             char c = PieceToChar(boards[move][i][j]);
14851             fputc(c == 'x' ? '.' : c, fp);
14852             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14853         }
14854     }
14855     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14856       fprintf(fp, "white to play\n");
14857     else
14858       fprintf(fp, "black to play\n");
14859 }
14860
14861 void
14862 PrintOpponents (FILE *fp)
14863 {
14864     if (gameInfo.white != NULL) {
14865         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14866     } else {
14867         fprintf(fp, "\n");
14868     }
14869 }
14870
14871 /* Find last component of program's own name, using some heuristics */
14872 void
14873 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14874 {
14875     char *p, *q, c;
14876     int local = (strcmp(host, "localhost") == 0);
14877     while (!local && (p = strchr(prog, ';')) != NULL) {
14878         p++;
14879         while (*p == ' ') p++;
14880         prog = p;
14881     }
14882     if (*prog == '"' || *prog == '\'') {
14883         q = strchr(prog + 1, *prog);
14884     } else {
14885         q = strchr(prog, ' ');
14886     }
14887     if (q == NULL) q = prog + strlen(prog);
14888     p = q;
14889     while (p >= prog && *p != '/' && *p != '\\') p--;
14890     p++;
14891     if(p == prog && *p == '"') p++;
14892     c = *q; *q = 0;
14893     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14894     memcpy(buf, p, q - p);
14895     buf[q - p] = NULLCHAR;
14896     if (!local) {
14897         strcat(buf, "@");
14898         strcat(buf, host);
14899     }
14900 }
14901
14902 char *
14903 TimeControlTagValue ()
14904 {
14905     char buf[MSG_SIZ];
14906     if (!appData.clockMode) {
14907       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14908     } else if (movesPerSession > 0) {
14909       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14910     } else if (timeIncrement == 0) {
14911       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14912     } else {
14913       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14914     }
14915     return StrSave(buf);
14916 }
14917
14918 void
14919 SetGameInfo ()
14920 {
14921     /* This routine is used only for certain modes */
14922     VariantClass v = gameInfo.variant;
14923     ChessMove r = GameUnfinished;
14924     char *p = NULL;
14925
14926     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14927         r = gameInfo.result;
14928         p = gameInfo.resultDetails;
14929         gameInfo.resultDetails = NULL;
14930     }
14931     ClearGameInfo(&gameInfo);
14932     gameInfo.variant = v;
14933
14934     switch (gameMode) {
14935       case MachinePlaysWhite:
14936         gameInfo.event = StrSave( appData.pgnEventHeader );
14937         gameInfo.site = StrSave(HostName());
14938         gameInfo.date = PGNDate();
14939         gameInfo.round = StrSave("-");
14940         gameInfo.white = StrSave(first.tidy);
14941         gameInfo.black = StrSave(UserName());
14942         gameInfo.timeControl = TimeControlTagValue();
14943         break;
14944
14945       case MachinePlaysBlack:
14946         gameInfo.event = StrSave( appData.pgnEventHeader );
14947         gameInfo.site = StrSave(HostName());
14948         gameInfo.date = PGNDate();
14949         gameInfo.round = StrSave("-");
14950         gameInfo.white = StrSave(UserName());
14951         gameInfo.black = StrSave(first.tidy);
14952         gameInfo.timeControl = TimeControlTagValue();
14953         break;
14954
14955       case TwoMachinesPlay:
14956         gameInfo.event = StrSave( appData.pgnEventHeader );
14957         gameInfo.site = StrSave(HostName());
14958         gameInfo.date = PGNDate();
14959         if (roundNr > 0) {
14960             char buf[MSG_SIZ];
14961             snprintf(buf, MSG_SIZ, "%d", roundNr);
14962             gameInfo.round = StrSave(buf);
14963         } else {
14964             gameInfo.round = StrSave("-");
14965         }
14966         if (first.twoMachinesColor[0] == 'w') {
14967             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14968             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14969         } else {
14970             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14971             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14972         }
14973         gameInfo.timeControl = TimeControlTagValue();
14974         break;
14975
14976       case EditGame:
14977         gameInfo.event = StrSave("Edited game");
14978         gameInfo.site = StrSave(HostName());
14979         gameInfo.date = PGNDate();
14980         gameInfo.round = StrSave("-");
14981         gameInfo.white = StrSave("-");
14982         gameInfo.black = StrSave("-");
14983         gameInfo.result = r;
14984         gameInfo.resultDetails = p;
14985         break;
14986
14987       case EditPosition:
14988         gameInfo.event = StrSave("Edited position");
14989         gameInfo.site = StrSave(HostName());
14990         gameInfo.date = PGNDate();
14991         gameInfo.round = StrSave("-");
14992         gameInfo.white = StrSave("-");
14993         gameInfo.black = StrSave("-");
14994         break;
14995
14996       case IcsPlayingWhite:
14997       case IcsPlayingBlack:
14998       case IcsObserving:
14999       case IcsExamining:
15000         break;
15001
15002       case PlayFromGameFile:
15003         gameInfo.event = StrSave("Game from non-PGN file");
15004         gameInfo.site = StrSave(HostName());
15005         gameInfo.date = PGNDate();
15006         gameInfo.round = StrSave("-");
15007         gameInfo.white = StrSave("?");
15008         gameInfo.black = StrSave("?");
15009         break;
15010
15011       default:
15012         break;
15013     }
15014 }
15015
15016 void
15017 ReplaceComment (int index, char *text)
15018 {
15019     int len;
15020     char *p;
15021     float score;
15022
15023     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15024        pvInfoList[index-1].depth == len &&
15025        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15026        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15027     while (*text == '\n') text++;
15028     len = strlen(text);
15029     while (len > 0 && text[len - 1] == '\n') len--;
15030
15031     if (commentList[index] != NULL)
15032       free(commentList[index]);
15033
15034     if (len == 0) {
15035         commentList[index] = NULL;
15036         return;
15037     }
15038   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15039       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15040       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15041     commentList[index] = (char *) malloc(len + 2);
15042     strncpy(commentList[index], text, len);
15043     commentList[index][len] = '\n';
15044     commentList[index][len + 1] = NULLCHAR;
15045   } else {
15046     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15047     char *p;
15048     commentList[index] = (char *) malloc(len + 7);
15049     safeStrCpy(commentList[index], "{\n", 3);
15050     safeStrCpy(commentList[index]+2, text, len+1);
15051     commentList[index][len+2] = NULLCHAR;
15052     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15053     strcat(commentList[index], "\n}\n");
15054   }
15055 }
15056
15057 void
15058 CrushCRs (char *text)
15059 {
15060   char *p = text;
15061   char *q = text;
15062   char ch;
15063
15064   do {
15065     ch = *p++;
15066     if (ch == '\r') continue;
15067     *q++ = ch;
15068   } while (ch != '\0');
15069 }
15070
15071 void
15072 AppendComment (int index, char *text, Boolean addBraces)
15073 /* addBraces  tells if we should add {} */
15074 {
15075     int oldlen, len;
15076     char *old;
15077
15078 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15079     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15080
15081     CrushCRs(text);
15082     while (*text == '\n') text++;
15083     len = strlen(text);
15084     while (len > 0 && text[len - 1] == '\n') len--;
15085     text[len] = NULLCHAR;
15086
15087     if (len == 0) return;
15088
15089     if (commentList[index] != NULL) {
15090       Boolean addClosingBrace = addBraces;
15091         old = commentList[index];
15092         oldlen = strlen(old);
15093         while(commentList[index][oldlen-1] ==  '\n')
15094           commentList[index][--oldlen] = NULLCHAR;
15095         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15096         safeStrCpy(commentList[index], old, oldlen + len + 6);
15097         free(old);
15098         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15099         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15100           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15101           while (*text == '\n') { text++; len--; }
15102           commentList[index][--oldlen] = NULLCHAR;
15103       }
15104         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15105         else          strcat(commentList[index], "\n");
15106         strcat(commentList[index], text);
15107         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15108         else          strcat(commentList[index], "\n");
15109     } else {
15110         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15111         if(addBraces)
15112           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15113         else commentList[index][0] = NULLCHAR;
15114         strcat(commentList[index], text);
15115         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15116         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15117     }
15118 }
15119
15120 static char *
15121 FindStr (char * text, char * sub_text)
15122 {
15123     char * result = strstr( text, sub_text );
15124
15125     if( result != NULL ) {
15126         result += strlen( sub_text );
15127     }
15128
15129     return result;
15130 }
15131
15132 /* [AS] Try to extract PV info from PGN comment */
15133 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15134 char *
15135 GetInfoFromComment (int index, char * text)
15136 {
15137     char * sep = text, *p;
15138
15139     if( text != NULL && index > 0 ) {
15140         int score = 0;
15141         int depth = 0;
15142         int time = -1, sec = 0, deci;
15143         char * s_eval = FindStr( text, "[%eval " );
15144         char * s_emt = FindStr( text, "[%emt " );
15145
15146         if( s_eval != NULL || s_emt != NULL ) {
15147             /* New style */
15148             char delim;
15149
15150             if( s_eval != NULL ) {
15151                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15152                     return text;
15153                 }
15154
15155                 if( delim != ']' ) {
15156                     return text;
15157                 }
15158             }
15159
15160             if( s_emt != NULL ) {
15161             }
15162                 return text;
15163         }
15164         else {
15165             /* We expect something like: [+|-]nnn.nn/dd */
15166             int score_lo = 0;
15167
15168             if(*text != '{') return text; // [HGM] braces: must be normal comment
15169
15170             sep = strchr( text, '/' );
15171             if( sep == NULL || sep < (text+4) ) {
15172                 return text;
15173             }
15174
15175             p = text;
15176             if(p[1] == '(') { // comment starts with PV
15177                p = strchr(p, ')'); // locate end of PV
15178                if(p == NULL || sep < p+5) return text;
15179                // at this point we have something like "{(.*) +0.23/6 ..."
15180                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15181                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15182                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15183             }
15184             time = -1; sec = -1; deci = -1;
15185             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15186                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15187                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15188                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15189                 return text;
15190             }
15191
15192             if( score_lo < 0 || score_lo >= 100 ) {
15193                 return text;
15194             }
15195
15196             if(sec >= 0) time = 600*time + 10*sec; else
15197             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15198
15199             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15200
15201             /* [HGM] PV time: now locate end of PV info */
15202             while( *++sep >= '0' && *sep <= '9'); // strip depth
15203             if(time >= 0)
15204             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15205             if(sec >= 0)
15206             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15207             if(deci >= 0)
15208             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15209             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15210         }
15211
15212         if( depth <= 0 ) {
15213             return text;
15214         }
15215
15216         if( time < 0 ) {
15217             time = -1;
15218         }
15219
15220         pvInfoList[index-1].depth = depth;
15221         pvInfoList[index-1].score = score;
15222         pvInfoList[index-1].time  = 10*time; // centi-sec
15223         if(*sep == '}') *sep = 0; else *--sep = '{';
15224         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15225     }
15226     return sep;
15227 }
15228
15229 void
15230 SendToProgram (char *message, ChessProgramState *cps)
15231 {
15232     int count, outCount, error;
15233     char buf[MSG_SIZ];
15234
15235     if (cps->pr == NoProc) return;
15236     Attention(cps);
15237
15238     if (appData.debugMode) {
15239         TimeMark now;
15240         GetTimeMark(&now);
15241         fprintf(debugFP, "%ld >%-6s: %s",
15242                 SubtractTimeMarks(&now, &programStartTime),
15243                 cps->which, message);
15244         if(serverFP)
15245             fprintf(serverFP, "%ld >%-6s: %s",
15246                 SubtractTimeMarks(&now, &programStartTime),
15247                 cps->which, message), fflush(serverFP);
15248     }
15249
15250     count = strlen(message);
15251     outCount = OutputToProcess(cps->pr, message, count, &error);
15252     if (outCount < count && !exiting
15253                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15254       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15255       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15256         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15257             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15258                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15259                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15260                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15261             } else {
15262                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15263                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15264                 gameInfo.result = res;
15265             }
15266             gameInfo.resultDetails = StrSave(buf);
15267         }
15268         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15269         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15270     }
15271 }
15272
15273 void
15274 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15275 {
15276     char *end_str;
15277     char buf[MSG_SIZ];
15278     ChessProgramState *cps = (ChessProgramState *)closure;
15279
15280     if (isr != cps->isr) return; /* Killed intentionally */
15281     if (count <= 0) {
15282         if (count == 0) {
15283             RemoveInputSource(cps->isr);
15284             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15285                     _(cps->which), cps->program);
15286             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15287             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15288                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15289                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15290                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15291                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15292                 } else {
15293                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15294                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15295                     gameInfo.result = res;
15296                 }
15297                 gameInfo.resultDetails = StrSave(buf);
15298             }
15299             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15300             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15301         } else {
15302             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15303                     _(cps->which), cps->program);
15304             RemoveInputSource(cps->isr);
15305
15306             /* [AS] Program is misbehaving badly... kill it */
15307             if( count == -2 ) {
15308                 DestroyChildProcess( cps->pr, 9 );
15309                 cps->pr = NoProc;
15310             }
15311
15312             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15313         }
15314         return;
15315     }
15316
15317     if ((end_str = strchr(message, '\r')) != NULL)
15318       *end_str = NULLCHAR;
15319     if ((end_str = strchr(message, '\n')) != NULL)
15320       *end_str = NULLCHAR;
15321
15322     if (appData.debugMode) {
15323         TimeMark now; int print = 1;
15324         char *quote = ""; char c; int i;
15325
15326         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15327                 char start = message[0];
15328                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15329                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15330                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15331                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15332                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15333                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15334                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15335                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15336                    sscanf(message, "hint: %c", &c)!=1 && 
15337                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15338                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15339                     print = (appData.engineComments >= 2);
15340                 }
15341                 message[0] = start; // restore original message
15342         }
15343         if(print) {
15344                 GetTimeMark(&now);
15345                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15346                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15347                         quote,
15348                         message);
15349                 if(serverFP)
15350                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15351                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15352                         quote,
15353                         message), fflush(serverFP);
15354         }
15355     }
15356
15357     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15358     if (appData.icsEngineAnalyze) {
15359         if (strstr(message, "whisper") != NULL ||
15360              strstr(message, "kibitz") != NULL ||
15361             strstr(message, "tellics") != NULL) return;
15362     }
15363
15364     HandleMachineMove(message, cps);
15365 }
15366
15367
15368 void
15369 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15370 {
15371     char buf[MSG_SIZ];
15372     int seconds;
15373
15374     if( timeControl_2 > 0 ) {
15375         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15376             tc = timeControl_2;
15377         }
15378     }
15379     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15380     inc /= cps->timeOdds;
15381     st  /= cps->timeOdds;
15382
15383     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15384
15385     if (st > 0) {
15386       /* Set exact time per move, normally using st command */
15387       if (cps->stKludge) {
15388         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15389         seconds = st % 60;
15390         if (seconds == 0) {
15391           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15392         } else {
15393           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15394         }
15395       } else {
15396         snprintf(buf, MSG_SIZ, "st %d\n", st);
15397       }
15398     } else {
15399       /* Set conventional or incremental time control, using level command */
15400       if (seconds == 0) {
15401         /* Note old gnuchess bug -- minutes:seconds used to not work.
15402            Fixed in later versions, but still avoid :seconds
15403            when seconds is 0. */
15404         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15405       } else {
15406         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15407                  seconds, inc/1000.);
15408       }
15409     }
15410     SendToProgram(buf, cps);
15411
15412     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15413     /* Orthogonally, limit search to given depth */
15414     if (sd > 0) {
15415       if (cps->sdKludge) {
15416         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15417       } else {
15418         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15419       }
15420       SendToProgram(buf, cps);
15421     }
15422
15423     if(cps->nps >= 0) { /* [HGM] nps */
15424         if(cps->supportsNPS == FALSE)
15425           cps->nps = -1; // don't use if engine explicitly says not supported!
15426         else {
15427           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15428           SendToProgram(buf, cps);
15429         }
15430     }
15431 }
15432
15433 ChessProgramState *
15434 WhitePlayer ()
15435 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15436 {
15437     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15438        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15439         return &second;
15440     return &first;
15441 }
15442
15443 void
15444 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15445 {
15446     char message[MSG_SIZ];
15447     long time, otime;
15448
15449     /* Note: this routine must be called when the clocks are stopped
15450        or when they have *just* been set or switched; otherwise
15451        it will be off by the time since the current tick started.
15452     */
15453     if (machineWhite) {
15454         time = whiteTimeRemaining / 10;
15455         otime = blackTimeRemaining / 10;
15456     } else {
15457         time = blackTimeRemaining / 10;
15458         otime = whiteTimeRemaining / 10;
15459     }
15460     /* [HGM] translate opponent's time by time-odds factor */
15461     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15462
15463     if (time <= 0) time = 1;
15464     if (otime <= 0) otime = 1;
15465
15466     snprintf(message, MSG_SIZ, "time %ld\n", time);
15467     SendToProgram(message, cps);
15468
15469     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15470     SendToProgram(message, cps);
15471 }
15472
15473 int
15474 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15475 {
15476   char buf[MSG_SIZ];
15477   int len = strlen(name);
15478   int val;
15479
15480   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15481     (*p) += len + 1;
15482     sscanf(*p, "%d", &val);
15483     *loc = (val != 0);
15484     while (**p && **p != ' ')
15485       (*p)++;
15486     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15487     SendToProgram(buf, cps);
15488     return TRUE;
15489   }
15490   return FALSE;
15491 }
15492
15493 int
15494 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15495 {
15496   char buf[MSG_SIZ];
15497   int len = strlen(name);
15498   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15499     (*p) += len + 1;
15500     sscanf(*p, "%d", loc);
15501     while (**p && **p != ' ') (*p)++;
15502     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15503     SendToProgram(buf, cps);
15504     return TRUE;
15505   }
15506   return FALSE;
15507 }
15508
15509 int
15510 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15511 {
15512   char buf[MSG_SIZ];
15513   int len = strlen(name);
15514   if (strncmp((*p), name, len) == 0
15515       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15516     (*p) += len + 2;
15517     sscanf(*p, "%[^\"]", loc);
15518     while (**p && **p != '\"') (*p)++;
15519     if (**p == '\"') (*p)++;
15520     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15521     SendToProgram(buf, cps);
15522     return TRUE;
15523   }
15524   return FALSE;
15525 }
15526
15527 int
15528 ParseOption (Option *opt, ChessProgramState *cps)
15529 // [HGM] options: process the string that defines an engine option, and determine
15530 // name, type, default value, and allowed value range
15531 {
15532         char *p, *q, buf[MSG_SIZ];
15533         int n, min = (-1)<<31, max = 1<<31, def;
15534
15535         if(p = strstr(opt->name, " -spin ")) {
15536             if((n = sscanf(p, " -spin %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;
15544         } else if((p = strstr(opt->name, " -slider "))) {
15545             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15546             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15547             if(max < min) max = min; // enforce consistency
15548             if(def < min) def = min;
15549             if(def > max) def = max;
15550             opt->value = def;
15551             opt->min = min;
15552             opt->max = max;
15553             opt->type = Spin; // Slider;
15554         } else if((p = strstr(opt->name, " -string "))) {
15555             opt->textValue = p+9;
15556             opt->type = TextBox;
15557         } else if((p = strstr(opt->name, " -file "))) {
15558             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15559             opt->textValue = p+7;
15560             opt->type = FileName; // FileName;
15561         } else if((p = strstr(opt->name, " -path "))) {
15562             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15563             opt->textValue = p+7;
15564             opt->type = PathName; // PathName;
15565         } else if(p = strstr(opt->name, " -check ")) {
15566             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15567             opt->value = (def != 0);
15568             opt->type = CheckBox;
15569         } else if(p = strstr(opt->name, " -combo ")) {
15570             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15571             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15572             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15573             opt->value = n = 0;
15574             while(q = StrStr(q, " /// ")) {
15575                 n++; *q = 0;    // count choices, and null-terminate each of them
15576                 q += 5;
15577                 if(*q == '*') { // remember default, which is marked with * prefix
15578                     q++;
15579                     opt->value = n;
15580                 }
15581                 cps->comboList[cps->comboCnt++] = q;
15582             }
15583             cps->comboList[cps->comboCnt++] = NULL;
15584             opt->max = n + 1;
15585             opt->type = ComboBox;
15586         } else if(p = strstr(opt->name, " -button")) {
15587             opt->type = Button;
15588         } else if(p = strstr(opt->name, " -save")) {
15589             opt->type = SaveButton;
15590         } else return FALSE;
15591         *p = 0; // terminate option name
15592         // now look if the command-line options define a setting for this engine option.
15593         if(cps->optionSettings && cps->optionSettings[0])
15594             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15595         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15596           snprintf(buf, MSG_SIZ, "option %s", p);
15597                 if(p = strstr(buf, ",")) *p = 0;
15598                 if(q = strchr(buf, '=')) switch(opt->type) {
15599                     case ComboBox:
15600                         for(n=0; n<opt->max; n++)
15601                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15602                         break;
15603                     case TextBox:
15604                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15605                         break;
15606                     case Spin:
15607                     case CheckBox:
15608                         opt->value = atoi(q+1);
15609                     default:
15610                         break;
15611                 }
15612                 strcat(buf, "\n");
15613                 SendToProgram(buf, cps);
15614         }
15615         return TRUE;
15616 }
15617
15618 void
15619 FeatureDone (ChessProgramState *cps, int val)
15620 {
15621   DelayedEventCallback cb = GetDelayedEvent();
15622   if ((cb == InitBackEnd3 && cps == &first) ||
15623       (cb == SettingsMenuIfReady && cps == &second) ||
15624       (cb == LoadEngine) ||
15625       (cb == TwoMachinesEventIfReady)) {
15626     CancelDelayedEvent();
15627     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15628   }
15629   cps->initDone = val;
15630 }
15631
15632 /* Parse feature command from engine */
15633 void
15634 ParseFeatures (char *args, ChessProgramState *cps)
15635 {
15636   char *p = args;
15637   char *q;
15638   int val;
15639   char buf[MSG_SIZ];
15640
15641   for (;;) {
15642     while (*p == ' ') p++;
15643     if (*p == NULLCHAR) return;
15644
15645     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15646     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15647     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15648     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15649     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15650     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15651     if (BoolFeature(&p, "reuse", &val, cps)) {
15652       /* Engine can disable reuse, but can't enable it if user said no */
15653       if (!val) cps->reuse = FALSE;
15654       continue;
15655     }
15656     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15657     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15658       if (gameMode == TwoMachinesPlay) {
15659         DisplayTwoMachinesTitle();
15660       } else {
15661         DisplayTitle("");
15662       }
15663       continue;
15664     }
15665     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15666     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15667     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15668     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15669     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15670     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15671     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15672     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15673     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15674     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15675     if (IntFeature(&p, "done", &val, cps)) {
15676       FeatureDone(cps, val);
15677       continue;
15678     }
15679     /* Added by Tord: */
15680     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15681     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15682     /* End of additions by Tord */
15683
15684     /* [HGM] added features: */
15685     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15686     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15687     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15688     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15689     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15690     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15691     if (StringFeature(&p, "option", buf, cps)) {
15692         FREE(cps->option[cps->nrOptions].name);
15693         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15694         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15695         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15696           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15697             SendToProgram(buf, cps);
15698             continue;
15699         }
15700         if(cps->nrOptions >= MAX_OPTIONS) {
15701             cps->nrOptions--;
15702             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15703             DisplayError(buf, 0);
15704         }
15705         continue;
15706     }
15707     /* End of additions by HGM */
15708
15709     /* unknown feature: complain and skip */
15710     q = p;
15711     while (*q && *q != '=') q++;
15712     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15713     SendToProgram(buf, cps);
15714     p = q;
15715     if (*p == '=') {
15716       p++;
15717       if (*p == '\"') {
15718         p++;
15719         while (*p && *p != '\"') p++;
15720         if (*p == '\"') p++;
15721       } else {
15722         while (*p && *p != ' ') p++;
15723       }
15724     }
15725   }
15726
15727 }
15728
15729 void
15730 PeriodicUpdatesEvent (int newState)
15731 {
15732     if (newState == appData.periodicUpdates)
15733       return;
15734
15735     appData.periodicUpdates=newState;
15736
15737     /* Display type changes, so update it now */
15738 //    DisplayAnalysis();
15739
15740     /* Get the ball rolling again... */
15741     if (newState) {
15742         AnalysisPeriodicEvent(1);
15743         StartAnalysisClock();
15744     }
15745 }
15746
15747 void
15748 PonderNextMoveEvent (int newState)
15749 {
15750     if (newState == appData.ponderNextMove) return;
15751     if (gameMode == EditPosition) EditPositionDone(TRUE);
15752     if (newState) {
15753         SendToProgram("hard\n", &first);
15754         if (gameMode == TwoMachinesPlay) {
15755             SendToProgram("hard\n", &second);
15756         }
15757     } else {
15758         SendToProgram("easy\n", &first);
15759         thinkOutput[0] = NULLCHAR;
15760         if (gameMode == TwoMachinesPlay) {
15761             SendToProgram("easy\n", &second);
15762         }
15763     }
15764     appData.ponderNextMove = newState;
15765 }
15766
15767 void
15768 NewSettingEvent (int option, int *feature, char *command, int value)
15769 {
15770     char buf[MSG_SIZ];
15771
15772     if (gameMode == EditPosition) EditPositionDone(TRUE);
15773     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15774     if(feature == NULL || *feature) SendToProgram(buf, &first);
15775     if (gameMode == TwoMachinesPlay) {
15776         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15777     }
15778 }
15779
15780 void
15781 ShowThinkingEvent ()
15782 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15783 {
15784     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15785     int newState = appData.showThinking
15786         // [HGM] thinking: other features now need thinking output as well
15787         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15788
15789     if (oldState == newState) return;
15790     oldState = newState;
15791     if (gameMode == EditPosition) EditPositionDone(TRUE);
15792     if (oldState) {
15793         SendToProgram("post\n", &first);
15794         if (gameMode == TwoMachinesPlay) {
15795             SendToProgram("post\n", &second);
15796         }
15797     } else {
15798         SendToProgram("nopost\n", &first);
15799         thinkOutput[0] = NULLCHAR;
15800         if (gameMode == TwoMachinesPlay) {
15801             SendToProgram("nopost\n", &second);
15802         }
15803     }
15804 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15805 }
15806
15807 void
15808 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15809 {
15810   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15811   if (pr == NoProc) return;
15812   AskQuestion(title, question, replyPrefix, pr);
15813 }
15814
15815 void
15816 TypeInEvent (char firstChar)
15817 {
15818     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15819         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15820         gameMode == AnalyzeMode || gameMode == EditGame || 
15821         gameMode == EditPosition || gameMode == IcsExamining ||
15822         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15823         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15824                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15825                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15826         gameMode == Training) PopUpMoveDialog(firstChar);
15827 }
15828
15829 void
15830 TypeInDoneEvent (char *move)
15831 {
15832         Board board;
15833         int n, fromX, fromY, toX, toY;
15834         char promoChar;
15835         ChessMove moveType;
15836
15837         // [HGM] FENedit
15838         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15839                 EditPositionPasteFEN(move);
15840                 return;
15841         }
15842         // [HGM] movenum: allow move number to be typed in any mode
15843         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15844           ToNrEvent(2*n-1);
15845           return;
15846         }
15847         // undocumented kludge: allow command-line option to be typed in!
15848         // (potentially fatal, and does not implement the effect of the option.)
15849         // should only be used for options that are values on which future decisions will be made,
15850         // and definitely not on options that would be used during initialization.
15851         if(strstr(move, "!!! -") == move) {
15852             ParseArgsFromString(move+4);
15853             return;
15854         }
15855
15856       if (gameMode != EditGame && currentMove != forwardMostMove && 
15857         gameMode != Training) {
15858         DisplayMoveError(_("Displayed move is not current"));
15859       } else {
15860         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15861           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15862         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15863         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15864           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15865           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15866         } else {
15867           DisplayMoveError(_("Could not parse move"));
15868         }
15869       }
15870 }
15871
15872 void
15873 DisplayMove (int moveNumber)
15874 {
15875     char message[MSG_SIZ];
15876     char res[MSG_SIZ];
15877     char cpThinkOutput[MSG_SIZ];
15878
15879     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15880
15881     if (moveNumber == forwardMostMove - 1 ||
15882         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15883
15884         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15885
15886         if (strchr(cpThinkOutput, '\n')) {
15887             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15888         }
15889     } else {
15890         *cpThinkOutput = NULLCHAR;
15891     }
15892
15893     /* [AS] Hide thinking from human user */
15894     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15895         *cpThinkOutput = NULLCHAR;
15896         if( thinkOutput[0] != NULLCHAR ) {
15897             int i;
15898
15899             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15900                 cpThinkOutput[i] = '.';
15901             }
15902             cpThinkOutput[i] = NULLCHAR;
15903             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15904         }
15905     }
15906
15907     if (moveNumber == forwardMostMove - 1 &&
15908         gameInfo.resultDetails != NULL) {
15909         if (gameInfo.resultDetails[0] == NULLCHAR) {
15910           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15911         } else {
15912           snprintf(res, MSG_SIZ, " {%s} %s",
15913                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15914         }
15915     } else {
15916         res[0] = NULLCHAR;
15917     }
15918
15919     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15920         DisplayMessage(res, cpThinkOutput);
15921     } else {
15922       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15923                 WhiteOnMove(moveNumber) ? " " : ".. ",
15924                 parseList[moveNumber], res);
15925         DisplayMessage(message, cpThinkOutput);
15926     }
15927 }
15928
15929 void
15930 DisplayComment (int moveNumber, char *text)
15931 {
15932     char title[MSG_SIZ];
15933
15934     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15935       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15936     } else {
15937       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15938               WhiteOnMove(moveNumber) ? " " : ".. ",
15939               parseList[moveNumber]);
15940     }
15941     if (text != NULL && (appData.autoDisplayComment || commentUp))
15942         CommentPopUp(title, text);
15943 }
15944
15945 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15946  * might be busy thinking or pondering.  It can be omitted if your
15947  * gnuchess is configured to stop thinking immediately on any user
15948  * input.  However, that gnuchess feature depends on the FIONREAD
15949  * ioctl, which does not work properly on some flavors of Unix.
15950  */
15951 void
15952 Attention (ChessProgramState *cps)
15953 {
15954 #if ATTENTION
15955     if (!cps->useSigint) return;
15956     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15957     switch (gameMode) {
15958       case MachinePlaysWhite:
15959       case MachinePlaysBlack:
15960       case TwoMachinesPlay:
15961       case IcsPlayingWhite:
15962       case IcsPlayingBlack:
15963       case AnalyzeMode:
15964       case AnalyzeFile:
15965         /* Skip if we know it isn't thinking */
15966         if (!cps->maybeThinking) return;
15967         if (appData.debugMode)
15968           fprintf(debugFP, "Interrupting %s\n", cps->which);
15969         InterruptChildProcess(cps->pr);
15970         cps->maybeThinking = FALSE;
15971         break;
15972       default:
15973         break;
15974     }
15975 #endif /*ATTENTION*/
15976 }
15977
15978 int
15979 CheckFlags ()
15980 {
15981     if (whiteTimeRemaining <= 0) {
15982         if (!whiteFlag) {
15983             whiteFlag = TRUE;
15984             if (appData.icsActive) {
15985                 if (appData.autoCallFlag &&
15986                     gameMode == IcsPlayingBlack && !blackFlag) {
15987                   SendToICS(ics_prefix);
15988                   SendToICS("flag\n");
15989                 }
15990             } else {
15991                 if (blackFlag) {
15992                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15993                 } else {
15994                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15995                     if (appData.autoCallFlag) {
15996                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15997                         return TRUE;
15998                     }
15999                 }
16000             }
16001         }
16002     }
16003     if (blackTimeRemaining <= 0) {
16004         if (!blackFlag) {
16005             blackFlag = TRUE;
16006             if (appData.icsActive) {
16007                 if (appData.autoCallFlag &&
16008                     gameMode == IcsPlayingWhite && !whiteFlag) {
16009                   SendToICS(ics_prefix);
16010                   SendToICS("flag\n");
16011                 }
16012             } else {
16013                 if (whiteFlag) {
16014                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16015                 } else {
16016                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16017                     if (appData.autoCallFlag) {
16018                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16019                         return TRUE;
16020                     }
16021                 }
16022             }
16023         }
16024     }
16025     return FALSE;
16026 }
16027
16028 void
16029 CheckTimeControl ()
16030 {
16031     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16032         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16033
16034     /*
16035      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16036      */
16037     if ( !WhiteOnMove(forwardMostMove) ) {
16038         /* White made time control */
16039         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16040         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16041         /* [HGM] time odds: correct new time quota for time odds! */
16042                                             / WhitePlayer()->timeOdds;
16043         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16044     } else {
16045         lastBlack -= blackTimeRemaining;
16046         /* Black made time control */
16047         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16048                                             / WhitePlayer()->other->timeOdds;
16049         lastWhite = whiteTimeRemaining;
16050     }
16051 }
16052
16053 void
16054 DisplayBothClocks ()
16055 {
16056     int wom = gameMode == EditPosition ?
16057       !blackPlaysFirst : WhiteOnMove(currentMove);
16058     DisplayWhiteClock(whiteTimeRemaining, wom);
16059     DisplayBlackClock(blackTimeRemaining, !wom);
16060 }
16061
16062
16063 /* Timekeeping seems to be a portability nightmare.  I think everyone
16064    has ftime(), but I'm really not sure, so I'm including some ifdefs
16065    to use other calls if you don't.  Clocks will be less accurate if
16066    you have neither ftime nor gettimeofday.
16067 */
16068
16069 /* VS 2008 requires the #include outside of the function */
16070 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16071 #include <sys/timeb.h>
16072 #endif
16073
16074 /* Get the current time as a TimeMark */
16075 void
16076 GetTimeMark (TimeMark *tm)
16077 {
16078 #if HAVE_GETTIMEOFDAY
16079
16080     struct timeval timeVal;
16081     struct timezone timeZone;
16082
16083     gettimeofday(&timeVal, &timeZone);
16084     tm->sec = (long) timeVal.tv_sec;
16085     tm->ms = (int) (timeVal.tv_usec / 1000L);
16086
16087 #else /*!HAVE_GETTIMEOFDAY*/
16088 #if HAVE_FTIME
16089
16090 // include <sys/timeb.h> / moved to just above start of function
16091     struct timeb timeB;
16092
16093     ftime(&timeB);
16094     tm->sec = (long) timeB.time;
16095     tm->ms = (int) timeB.millitm;
16096
16097 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16098     tm->sec = (long) time(NULL);
16099     tm->ms = 0;
16100 #endif
16101 #endif
16102 }
16103
16104 /* Return the difference in milliseconds between two
16105    time marks.  We assume the difference will fit in a long!
16106 */
16107 long
16108 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16109 {
16110     return 1000L*(tm2->sec - tm1->sec) +
16111            (long) (tm2->ms - tm1->ms);
16112 }
16113
16114
16115 /*
16116  * Code to manage the game clocks.
16117  *
16118  * In tournament play, black starts the clock and then white makes a move.
16119  * We give the human user a slight advantage if he is playing white---the
16120  * clocks don't run until he makes his first move, so it takes zero time.
16121  * Also, we don't account for network lag, so we could get out of sync
16122  * with GNU Chess's clock -- but then, referees are always right.
16123  */
16124
16125 static TimeMark tickStartTM;
16126 static long intendedTickLength;
16127
16128 long
16129 NextTickLength (long timeRemaining)
16130 {
16131     long nominalTickLength, nextTickLength;
16132
16133     if (timeRemaining > 0L && timeRemaining <= 10000L)
16134       nominalTickLength = 100L;
16135     else
16136       nominalTickLength = 1000L;
16137     nextTickLength = timeRemaining % nominalTickLength;
16138     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16139
16140     return nextTickLength;
16141 }
16142
16143 /* Adjust clock one minute up or down */
16144 void
16145 AdjustClock (Boolean which, int dir)
16146 {
16147     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16148     if(which) blackTimeRemaining += 60000*dir;
16149     else      whiteTimeRemaining += 60000*dir;
16150     DisplayBothClocks();
16151     adjustedClock = TRUE;
16152 }
16153
16154 /* Stop clocks and reset to a fresh time control */
16155 void
16156 ResetClocks ()
16157 {
16158     (void) StopClockTimer();
16159     if (appData.icsActive) {
16160         whiteTimeRemaining = blackTimeRemaining = 0;
16161     } else if (searchTime) {
16162         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16163         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16164     } else { /* [HGM] correct new time quote for time odds */
16165         whiteTC = blackTC = fullTimeControlString;
16166         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16167         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16168     }
16169     if (whiteFlag || blackFlag) {
16170         DisplayTitle("");
16171         whiteFlag = blackFlag = FALSE;
16172     }
16173     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16174     DisplayBothClocks();
16175     adjustedClock = FALSE;
16176 }
16177
16178 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16179
16180 /* Decrement running clock by amount of time that has passed */
16181 void
16182 DecrementClocks ()
16183 {
16184     long timeRemaining;
16185     long lastTickLength, fudge;
16186     TimeMark now;
16187
16188     if (!appData.clockMode) return;
16189     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16190
16191     GetTimeMark(&now);
16192
16193     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16194
16195     /* Fudge if we woke up a little too soon */
16196     fudge = intendedTickLength - lastTickLength;
16197     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16198
16199     if (WhiteOnMove(forwardMostMove)) {
16200         if(whiteNPS >= 0) lastTickLength = 0;
16201         timeRemaining = whiteTimeRemaining -= lastTickLength;
16202         if(timeRemaining < 0 && !appData.icsActive) {
16203             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16204             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16205                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16206                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16207             }
16208         }
16209         DisplayWhiteClock(whiteTimeRemaining - fudge,
16210                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16211     } else {
16212         if(blackNPS >= 0) lastTickLength = 0;
16213         timeRemaining = blackTimeRemaining -= lastTickLength;
16214         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16215             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16216             if(suddenDeath) {
16217                 blackStartMove = forwardMostMove;
16218                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16219             }
16220         }
16221         DisplayBlackClock(blackTimeRemaining - fudge,
16222                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16223     }
16224     if (CheckFlags()) return;
16225
16226     if(twoBoards) { // count down secondary board's clocks as well
16227         activePartnerTime -= lastTickLength;
16228         partnerUp = 1;
16229         if(activePartner == 'W')
16230             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16231         else
16232             DisplayBlackClock(activePartnerTime, TRUE);
16233         partnerUp = 0;
16234     }
16235
16236     tickStartTM = now;
16237     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16238     StartClockTimer(intendedTickLength);
16239
16240     /* if the time remaining has fallen below the alarm threshold, sound the
16241      * alarm. if the alarm has sounded and (due to a takeback or time control
16242      * with increment) the time remaining has increased to a level above the
16243      * threshold, reset the alarm so it can sound again.
16244      */
16245
16246     if (appData.icsActive && appData.icsAlarm) {
16247
16248         /* make sure we are dealing with the user's clock */
16249         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16250                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16251            )) return;
16252
16253         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16254             alarmSounded = FALSE;
16255         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16256             PlayAlarmSound();
16257             alarmSounded = TRUE;
16258         }
16259     }
16260 }
16261
16262
16263 /* A player has just moved, so stop the previously running
16264    clock and (if in clock mode) start the other one.
16265    We redisplay both clocks in case we're in ICS mode, because
16266    ICS gives us an update to both clocks after every move.
16267    Note that this routine is called *after* forwardMostMove
16268    is updated, so the last fractional tick must be subtracted
16269    from the color that is *not* on move now.
16270 */
16271 void
16272 SwitchClocks (int newMoveNr)
16273 {
16274     long lastTickLength;
16275     TimeMark now;
16276     int flagged = FALSE;
16277
16278     GetTimeMark(&now);
16279
16280     if (StopClockTimer() && appData.clockMode) {
16281         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16282         if (!WhiteOnMove(forwardMostMove)) {
16283             if(blackNPS >= 0) lastTickLength = 0;
16284             blackTimeRemaining -= lastTickLength;
16285            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16286 //         if(pvInfoList[forwardMostMove].time == -1)
16287                  pvInfoList[forwardMostMove].time =               // use GUI time
16288                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16289         } else {
16290            if(whiteNPS >= 0) lastTickLength = 0;
16291            whiteTimeRemaining -= lastTickLength;
16292            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16293 //         if(pvInfoList[forwardMostMove].time == -1)
16294                  pvInfoList[forwardMostMove].time =
16295                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16296         }
16297         flagged = CheckFlags();
16298     }
16299     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16300     CheckTimeControl();
16301
16302     if (flagged || !appData.clockMode) return;
16303
16304     switch (gameMode) {
16305       case MachinePlaysBlack:
16306       case MachinePlaysWhite:
16307       case BeginningOfGame:
16308         if (pausing) return;
16309         break;
16310
16311       case EditGame:
16312       case PlayFromGameFile:
16313       case IcsExamining:
16314         return;
16315
16316       default:
16317         break;
16318     }
16319
16320     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16321         if(WhiteOnMove(forwardMostMove))
16322              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16323         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16324     }
16325
16326     tickStartTM = now;
16327     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16328       whiteTimeRemaining : blackTimeRemaining);
16329     StartClockTimer(intendedTickLength);
16330 }
16331
16332
16333 /* Stop both clocks */
16334 void
16335 StopClocks ()
16336 {
16337     long lastTickLength;
16338     TimeMark now;
16339
16340     if (!StopClockTimer()) return;
16341     if (!appData.clockMode) return;
16342
16343     GetTimeMark(&now);
16344
16345     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16346     if (WhiteOnMove(forwardMostMove)) {
16347         if(whiteNPS >= 0) lastTickLength = 0;
16348         whiteTimeRemaining -= lastTickLength;
16349         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16350     } else {
16351         if(blackNPS >= 0) lastTickLength = 0;
16352         blackTimeRemaining -= lastTickLength;
16353         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16354     }
16355     CheckFlags();
16356 }
16357
16358 /* Start clock of player on move.  Time may have been reset, so
16359    if clock is already running, stop and restart it. */
16360 void
16361 StartClocks ()
16362 {
16363     (void) StopClockTimer(); /* in case it was running already */
16364     DisplayBothClocks();
16365     if (CheckFlags()) return;
16366
16367     if (!appData.clockMode) return;
16368     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16369
16370     GetTimeMark(&tickStartTM);
16371     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16372       whiteTimeRemaining : blackTimeRemaining);
16373
16374    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16375     whiteNPS = blackNPS = -1;
16376     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16377        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16378         whiteNPS = first.nps;
16379     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16380        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16381         blackNPS = first.nps;
16382     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16383         whiteNPS = second.nps;
16384     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16385         blackNPS = second.nps;
16386     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16387
16388     StartClockTimer(intendedTickLength);
16389 }
16390
16391 char *
16392 TimeString (long ms)
16393 {
16394     long second, minute, hour, day;
16395     char *sign = "";
16396     static char buf[32];
16397
16398     if (ms > 0 && ms <= 9900) {
16399       /* convert milliseconds to tenths, rounding up */
16400       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16401
16402       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16403       return buf;
16404     }
16405
16406     /* convert milliseconds to seconds, rounding up */
16407     /* use floating point to avoid strangeness of integer division
16408        with negative dividends on many machines */
16409     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16410
16411     if (second < 0) {
16412         sign = "-";
16413         second = -second;
16414     }
16415
16416     day = second / (60 * 60 * 24);
16417     second = second % (60 * 60 * 24);
16418     hour = second / (60 * 60);
16419     second = second % (60 * 60);
16420     minute = second / 60;
16421     second = second % 60;
16422
16423     if (day > 0)
16424       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16425               sign, day, hour, minute, second);
16426     else if (hour > 0)
16427       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16428     else
16429       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16430
16431     return buf;
16432 }
16433
16434
16435 /*
16436  * This is necessary because some C libraries aren't ANSI C compliant yet.
16437  */
16438 char *
16439 StrStr (char *string, char *match)
16440 {
16441     int i, length;
16442
16443     length = strlen(match);
16444
16445     for (i = strlen(string) - length; i >= 0; i--, string++)
16446       if (!strncmp(match, string, length))
16447         return string;
16448
16449     return NULL;
16450 }
16451
16452 char *
16453 StrCaseStr (char *string, char *match)
16454 {
16455     int i, j, length;
16456
16457     length = strlen(match);
16458
16459     for (i = strlen(string) - length; i >= 0; i--, string++) {
16460         for (j = 0; j < length; j++) {
16461             if (ToLower(match[j]) != ToLower(string[j]))
16462               break;
16463         }
16464         if (j == length) return string;
16465     }
16466
16467     return NULL;
16468 }
16469
16470 #ifndef _amigados
16471 int
16472 StrCaseCmp (char *s1, char *s2)
16473 {
16474     char c1, c2;
16475
16476     for (;;) {
16477         c1 = ToLower(*s1++);
16478         c2 = ToLower(*s2++);
16479         if (c1 > c2) return 1;
16480         if (c1 < c2) return -1;
16481         if (c1 == NULLCHAR) return 0;
16482     }
16483 }
16484
16485
16486 int
16487 ToLower (int c)
16488 {
16489     return isupper(c) ? tolower(c) : c;
16490 }
16491
16492
16493 int
16494 ToUpper (int c)
16495 {
16496     return islower(c) ? toupper(c) : c;
16497 }
16498 #endif /* !_amigados    */
16499
16500 char *
16501 StrSave (char *s)
16502 {
16503   char *ret;
16504
16505   if ((ret = (char *) malloc(strlen(s) + 1)))
16506     {
16507       safeStrCpy(ret, s, strlen(s)+1);
16508     }
16509   return ret;
16510 }
16511
16512 char *
16513 StrSavePtr (char *s, char **savePtr)
16514 {
16515     if (*savePtr) {
16516         free(*savePtr);
16517     }
16518     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16519       safeStrCpy(*savePtr, s, strlen(s)+1);
16520     }
16521     return(*savePtr);
16522 }
16523
16524 char *
16525 PGNDate ()
16526 {
16527     time_t clock;
16528     struct tm *tm;
16529     char buf[MSG_SIZ];
16530
16531     clock = time((time_t *)NULL);
16532     tm = localtime(&clock);
16533     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16534             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16535     return StrSave(buf);
16536 }
16537
16538
16539 char *
16540 PositionToFEN (int move, char *overrideCastling)
16541 {
16542     int i, j, fromX, fromY, toX, toY;
16543     int whiteToPlay;
16544     char buf[MSG_SIZ];
16545     char *p, *q;
16546     int emptycount;
16547     ChessSquare piece;
16548
16549     whiteToPlay = (gameMode == EditPosition) ?
16550       !blackPlaysFirst : (move % 2 == 0);
16551     p = buf;
16552
16553     /* Piece placement data */
16554     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16555         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16556         emptycount = 0;
16557         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16558             if (boards[move][i][j] == EmptySquare) {
16559                 emptycount++;
16560             } else { ChessSquare piece = boards[move][i][j];
16561                 if (emptycount > 0) {
16562                     if(emptycount<10) /* [HGM] can be >= 10 */
16563                         *p++ = '0' + emptycount;
16564                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16565                     emptycount = 0;
16566                 }
16567                 if(PieceToChar(piece) == '+') {
16568                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16569                     *p++ = '+';
16570                     piece = (ChessSquare)(DEMOTED piece);
16571                 }
16572                 *p++ = PieceToChar(piece);
16573                 if(p[-1] == '~') {
16574                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16575                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16576                     *p++ = '~';
16577                 }
16578             }
16579         }
16580         if (emptycount > 0) {
16581             if(emptycount<10) /* [HGM] can be >= 10 */
16582                 *p++ = '0' + emptycount;
16583             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16584             emptycount = 0;
16585         }
16586         *p++ = '/';
16587     }
16588     *(p - 1) = ' ';
16589
16590     /* [HGM] print Crazyhouse or Shogi holdings */
16591     if( gameInfo.holdingsWidth ) {
16592         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16593         q = p;
16594         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16595             piece = boards[move][i][BOARD_WIDTH-1];
16596             if( piece != EmptySquare )
16597               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16598                   *p++ = PieceToChar(piece);
16599         }
16600         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16601             piece = boards[move][BOARD_HEIGHT-i-1][0];
16602             if( piece != EmptySquare )
16603               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16604                   *p++ = PieceToChar(piece);
16605         }
16606
16607         if( q == p ) *p++ = '-';
16608         *p++ = ']';
16609         *p++ = ' ';
16610     }
16611
16612     /* Active color */
16613     *p++ = whiteToPlay ? 'w' : 'b';
16614     *p++ = ' ';
16615
16616   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16617     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16618   } else {
16619   if(nrCastlingRights) {
16620      q = p;
16621      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16622        /* [HGM] write directly from rights */
16623            if(boards[move][CASTLING][2] != NoRights &&
16624               boards[move][CASTLING][0] != NoRights   )
16625                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16626            if(boards[move][CASTLING][2] != NoRights &&
16627               boards[move][CASTLING][1] != NoRights   )
16628                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16629            if(boards[move][CASTLING][5] != NoRights &&
16630               boards[move][CASTLING][3] != NoRights   )
16631                 *p++ = boards[move][CASTLING][3] + AAA;
16632            if(boards[move][CASTLING][5] != NoRights &&
16633               boards[move][CASTLING][4] != NoRights   )
16634                 *p++ = boards[move][CASTLING][4] + AAA;
16635      } else {
16636
16637         /* [HGM] write true castling rights */
16638         if( nrCastlingRights == 6 ) {
16639             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16640                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16641             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16642                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16643             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16644                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16645             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16646                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16647         }
16648      }
16649      if (q == p) *p++ = '-'; /* No castling rights */
16650      *p++ = ' ';
16651   }
16652
16653   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16654      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16655     /* En passant target square */
16656     if (move > backwardMostMove) {
16657         fromX = moveList[move - 1][0] - AAA;
16658         fromY = moveList[move - 1][1] - ONE;
16659         toX = moveList[move - 1][2] - AAA;
16660         toY = moveList[move - 1][3] - ONE;
16661         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16662             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16663             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16664             fromX == toX) {
16665             /* 2-square pawn move just happened */
16666             *p++ = toX + AAA;
16667             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16668         } else {
16669             *p++ = '-';
16670         }
16671     } else if(move == backwardMostMove) {
16672         // [HGM] perhaps we should always do it like this, and forget the above?
16673         if((signed char)boards[move][EP_STATUS] >= 0) {
16674             *p++ = boards[move][EP_STATUS] + AAA;
16675             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16676         } else {
16677             *p++ = '-';
16678         }
16679     } else {
16680         *p++ = '-';
16681     }
16682     *p++ = ' ';
16683   }
16684   }
16685
16686     /* [HGM] find reversible plies */
16687     {   int i = 0, j=move;
16688
16689         if (appData.debugMode) { int k;
16690             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16691             for(k=backwardMostMove; k<=forwardMostMove; k++)
16692                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16693
16694         }
16695
16696         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16697         if( j == backwardMostMove ) i += initialRulePlies;
16698         sprintf(p, "%d ", i);
16699         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16700     }
16701     /* Fullmove number */
16702     sprintf(p, "%d", (move / 2) + 1);
16703
16704     return StrSave(buf);
16705 }
16706
16707 Boolean
16708 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16709 {
16710     int i, j;
16711     char *p, c;
16712     int emptycount;
16713     ChessSquare piece;
16714
16715     p = fen;
16716
16717     /* [HGM] by default clear Crazyhouse holdings, if present */
16718     if(gameInfo.holdingsWidth) {
16719        for(i=0; i<BOARD_HEIGHT; i++) {
16720            board[i][0]             = EmptySquare; /* black holdings */
16721            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16722            board[i][1]             = (ChessSquare) 0; /* black counts */
16723            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16724        }
16725     }
16726
16727     /* Piece placement data */
16728     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16729         j = 0;
16730         for (;;) {
16731             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16732                 if (*p == '/') p++;
16733                 emptycount = gameInfo.boardWidth - j;
16734                 while (emptycount--)
16735                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16736                 break;
16737 #if(BOARD_FILES >= 10)
16738             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16739                 p++; emptycount=10;
16740                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16741                 while (emptycount--)
16742                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16743 #endif
16744             } else if (isdigit(*p)) {
16745                 emptycount = *p++ - '0';
16746                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16747                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16748                 while (emptycount--)
16749                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16750             } else if (*p == '+' || isalpha(*p)) {
16751                 if (j >= gameInfo.boardWidth) return FALSE;
16752                 if(*p=='+') {
16753                     piece = CharToPiece(*++p);
16754                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16755                     piece = (ChessSquare) (PROMOTED piece ); p++;
16756                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16757                 } else piece = CharToPiece(*p++);
16758
16759                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16760                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16761                     piece = (ChessSquare) (PROMOTED piece);
16762                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16763                     p++;
16764                 }
16765                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16766             } else {
16767                 return FALSE;
16768             }
16769         }
16770     }
16771     while (*p == '/' || *p == ' ') p++;
16772
16773     /* [HGM] look for Crazyhouse holdings here */
16774     while(*p==' ') p++;
16775     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16776         if(*p == '[') p++;
16777         if(*p == '-' ) p++; /* empty holdings */ else {
16778             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16779             /* if we would allow FEN reading to set board size, we would   */
16780             /* have to add holdings and shift the board read so far here   */
16781             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16782                 p++;
16783                 if((int) piece >= (int) BlackPawn ) {
16784                     i = (int)piece - (int)BlackPawn;
16785                     i = PieceToNumber((ChessSquare)i);
16786                     if( i >= gameInfo.holdingsSize ) return FALSE;
16787                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16788                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16789                 } else {
16790                     i = (int)piece - (int)WhitePawn;
16791                     i = PieceToNumber((ChessSquare)i);
16792                     if( i >= gameInfo.holdingsSize ) return FALSE;
16793                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16794                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16795                 }
16796             }
16797         }
16798         if(*p == ']') p++;
16799     }
16800
16801     while(*p == ' ') p++;
16802
16803     /* Active color */
16804     c = *p++;
16805     if(appData.colorNickNames) {
16806       if( c == appData.colorNickNames[0] ) c = 'w'; else
16807       if( c == appData.colorNickNames[1] ) c = 'b';
16808     }
16809     switch (c) {
16810       case 'w':
16811         *blackPlaysFirst = FALSE;
16812         break;
16813       case 'b':
16814         *blackPlaysFirst = TRUE;
16815         break;
16816       default:
16817         return FALSE;
16818     }
16819
16820     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16821     /* return the extra info in global variiables             */
16822
16823     /* set defaults in case FEN is incomplete */
16824     board[EP_STATUS] = EP_UNKNOWN;
16825     for(i=0; i<nrCastlingRights; i++ ) {
16826         board[CASTLING][i] =
16827             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16828     }   /* assume possible unless obviously impossible */
16829     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16830     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16831     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16832                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16833     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16834     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16835     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16836                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16837     FENrulePlies = 0;
16838
16839     while(*p==' ') p++;
16840     if(nrCastlingRights) {
16841       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16842           /* castling indicator present, so default becomes no castlings */
16843           for(i=0; i<nrCastlingRights; i++ ) {
16844                  board[CASTLING][i] = NoRights;
16845           }
16846       }
16847       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16848              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16849              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16850              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16851         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16852
16853         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16854             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16855             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16856         }
16857         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16858             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16859         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16860                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16861         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16862                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16863         switch(c) {
16864           case'K':
16865               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16866               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16867               board[CASTLING][2] = whiteKingFile;
16868               break;
16869           case'Q':
16870               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16871               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16872               board[CASTLING][2] = whiteKingFile;
16873               break;
16874           case'k':
16875               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16876               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16877               board[CASTLING][5] = blackKingFile;
16878               break;
16879           case'q':
16880               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16881               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16882               board[CASTLING][5] = blackKingFile;
16883           case '-':
16884               break;
16885           default: /* FRC castlings */
16886               if(c >= 'a') { /* black rights */
16887                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16888                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16889                   if(i == BOARD_RGHT) break;
16890                   board[CASTLING][5] = i;
16891                   c -= AAA;
16892                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16893                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16894                   if(c > i)
16895                       board[CASTLING][3] = c;
16896                   else
16897                       board[CASTLING][4] = c;
16898               } else { /* white rights */
16899                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16900                     if(board[0][i] == WhiteKing) break;
16901                   if(i == BOARD_RGHT) break;
16902                   board[CASTLING][2] = i;
16903                   c -= AAA - 'a' + 'A';
16904                   if(board[0][c] >= WhiteKing) break;
16905                   if(c > i)
16906                       board[CASTLING][0] = c;
16907                   else
16908                       board[CASTLING][1] = c;
16909               }
16910         }
16911       }
16912       for(i=0; i<nrCastlingRights; i++)
16913         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16914     if (appData.debugMode) {
16915         fprintf(debugFP, "FEN castling rights:");
16916         for(i=0; i<nrCastlingRights; i++)
16917         fprintf(debugFP, " %d", board[CASTLING][i]);
16918         fprintf(debugFP, "\n");
16919     }
16920
16921       while(*p==' ') p++;
16922     }
16923
16924     /* read e.p. field in games that know e.p. capture */
16925     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16926        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16927       if(*p=='-') {
16928         p++; board[EP_STATUS] = EP_NONE;
16929       } else {
16930          char c = *p++ - AAA;
16931
16932          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16933          if(*p >= '0' && *p <='9') p++;
16934          board[EP_STATUS] = c;
16935       }
16936     }
16937
16938
16939     if(sscanf(p, "%d", &i) == 1) {
16940         FENrulePlies = i; /* 50-move ply counter */
16941         /* (The move number is still ignored)    */
16942     }
16943
16944     return TRUE;
16945 }
16946
16947 void
16948 EditPositionPasteFEN (char *fen)
16949 {
16950   if (fen != NULL) {
16951     Board initial_position;
16952
16953     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16954       DisplayError(_("Bad FEN position in clipboard"), 0);
16955       return ;
16956     } else {
16957       int savedBlackPlaysFirst = blackPlaysFirst;
16958       EditPositionEvent();
16959       blackPlaysFirst = savedBlackPlaysFirst;
16960       CopyBoard(boards[0], initial_position);
16961       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16962       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16963       DisplayBothClocks();
16964       DrawPosition(FALSE, boards[currentMove]);
16965     }
16966   }
16967 }
16968
16969 static char cseq[12] = "\\   ";
16970
16971 Boolean
16972 set_cont_sequence (char *new_seq)
16973 {
16974     int len;
16975     Boolean ret;
16976
16977     // handle bad attempts to set the sequence
16978         if (!new_seq)
16979                 return 0; // acceptable error - no debug
16980
16981     len = strlen(new_seq);
16982     ret = (len > 0) && (len < sizeof(cseq));
16983     if (ret)
16984       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16985     else if (appData.debugMode)
16986       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16987     return ret;
16988 }
16989
16990 /*
16991     reformat a source message so words don't cross the width boundary.  internal
16992     newlines are not removed.  returns the wrapped size (no null character unless
16993     included in source message).  If dest is NULL, only calculate the size required
16994     for the dest buffer.  lp argument indicats line position upon entry, and it's
16995     passed back upon exit.
16996 */
16997 int
16998 wrap (char *dest, char *src, int count, int width, int *lp)
16999 {
17000     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17001
17002     cseq_len = strlen(cseq);
17003     old_line = line = *lp;
17004     ansi = len = clen = 0;
17005
17006     for (i=0; i < count; i++)
17007     {
17008         if (src[i] == '\033')
17009             ansi = 1;
17010
17011         // if we hit the width, back up
17012         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17013         {
17014             // store i & len in case the word is too long
17015             old_i = i, old_len = len;
17016
17017             // find the end of the last word
17018             while (i && src[i] != ' ' && src[i] != '\n')
17019             {
17020                 i--;
17021                 len--;
17022             }
17023
17024             // word too long?  restore i & len before splitting it
17025             if ((old_i-i+clen) >= width)
17026             {
17027                 i = old_i;
17028                 len = old_len;
17029             }
17030
17031             // extra space?
17032             if (i && src[i-1] == ' ')
17033                 len--;
17034
17035             if (src[i] != ' ' && src[i] != '\n')
17036             {
17037                 i--;
17038                 if (len)
17039                     len--;
17040             }
17041
17042             // now append the newline and continuation sequence
17043             if (dest)
17044                 dest[len] = '\n';
17045             len++;
17046             if (dest)
17047                 strncpy(dest+len, cseq, cseq_len);
17048             len += cseq_len;
17049             line = cseq_len;
17050             clen = cseq_len;
17051             continue;
17052         }
17053
17054         if (dest)
17055             dest[len] = src[i];
17056         len++;
17057         if (!ansi)
17058             line++;
17059         if (src[i] == '\n')
17060             line = 0;
17061         if (src[i] == 'm')
17062             ansi = 0;
17063     }
17064     if (dest && appData.debugMode)
17065     {
17066         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17067             count, width, line, len, *lp);
17068         show_bytes(debugFP, src, count);
17069         fprintf(debugFP, "\ndest: ");
17070         show_bytes(debugFP, dest, len);
17071         fprintf(debugFP, "\n");
17072     }
17073     *lp = dest ? line : old_line;
17074
17075     return len;
17076 }
17077
17078 // [HGM] vari: routines for shelving variations
17079 Boolean modeRestore = FALSE;
17080
17081 void
17082 PushInner (int firstMove, int lastMove)
17083 {
17084         int i, j, nrMoves = lastMove - firstMove;
17085
17086         // push current tail of game on stack
17087         savedResult[storedGames] = gameInfo.result;
17088         savedDetails[storedGames] = gameInfo.resultDetails;
17089         gameInfo.resultDetails = NULL;
17090         savedFirst[storedGames] = firstMove;
17091         savedLast [storedGames] = lastMove;
17092         savedFramePtr[storedGames] = framePtr;
17093         framePtr -= nrMoves; // reserve space for the boards
17094         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17095             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17096             for(j=0; j<MOVE_LEN; j++)
17097                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17098             for(j=0; j<2*MOVE_LEN; j++)
17099                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17100             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17101             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17102             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17103             pvInfoList[firstMove+i-1].depth = 0;
17104             commentList[framePtr+i] = commentList[firstMove+i];
17105             commentList[firstMove+i] = NULL;
17106         }
17107
17108         storedGames++;
17109         forwardMostMove = firstMove; // truncate game so we can start variation
17110 }
17111
17112 void
17113 PushTail (int firstMove, int lastMove)
17114 {
17115         if(appData.icsActive) { // only in local mode
17116                 forwardMostMove = currentMove; // mimic old ICS behavior
17117                 return;
17118         }
17119         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17120
17121         PushInner(firstMove, lastMove);
17122         if(storedGames == 1) GreyRevert(FALSE);
17123         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17124 }
17125
17126 void
17127 PopInner (Boolean annotate)
17128 {
17129         int i, j, nrMoves;
17130         char buf[8000], moveBuf[20];
17131
17132         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17133         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17134         nrMoves = savedLast[storedGames] - currentMove;
17135         if(annotate) {
17136                 int cnt = 10;
17137                 if(!WhiteOnMove(currentMove))
17138                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17139                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17140                 for(i=currentMove; i<forwardMostMove; i++) {
17141                         if(WhiteOnMove(i))
17142                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17143                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17144                         strcat(buf, moveBuf);
17145                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17146                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17147                 }
17148                 strcat(buf, ")");
17149         }
17150         for(i=1; i<=nrMoves; i++) { // copy last variation back
17151             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17152             for(j=0; j<MOVE_LEN; j++)
17153                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17154             for(j=0; j<2*MOVE_LEN; j++)
17155                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17156             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17157             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17158             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17159             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17160             commentList[currentMove+i] = commentList[framePtr+i];
17161             commentList[framePtr+i] = NULL;
17162         }
17163         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17164         framePtr = savedFramePtr[storedGames];
17165         gameInfo.result = savedResult[storedGames];
17166         if(gameInfo.resultDetails != NULL) {
17167             free(gameInfo.resultDetails);
17168       }
17169         gameInfo.resultDetails = savedDetails[storedGames];
17170         forwardMostMove = currentMove + nrMoves;
17171 }
17172
17173 Boolean
17174 PopTail (Boolean annotate)
17175 {
17176         if(appData.icsActive) return FALSE; // only in local mode
17177         if(!storedGames) return FALSE; // sanity
17178         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17179
17180         PopInner(annotate);
17181         if(currentMove < forwardMostMove) ForwardEvent(); else
17182         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17183
17184         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17185         return TRUE;
17186 }
17187
17188 void
17189 CleanupTail ()
17190 {       // remove all shelved variations
17191         int i;
17192         for(i=0; i<storedGames; i++) {
17193             if(savedDetails[i])
17194                 free(savedDetails[i]);
17195             savedDetails[i] = NULL;
17196         }
17197         for(i=framePtr; i<MAX_MOVES; i++) {
17198                 if(commentList[i]) free(commentList[i]);
17199                 commentList[i] = NULL;
17200         }
17201         framePtr = MAX_MOVES-1;
17202         storedGames = 0;
17203 }
17204
17205 void
17206 LoadVariation (int index, char *text)
17207 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17208         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17209         int level = 0, move;
17210
17211         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17212         // first find outermost bracketing variation
17213         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17214             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17215                 if(*p == '{') wait = '}'; else
17216                 if(*p == '[') wait = ']'; else
17217                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17218                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17219             }
17220             if(*p == wait) wait = NULLCHAR; // closing ]} found
17221             p++;
17222         }
17223         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17224         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17225         end[1] = NULLCHAR; // clip off comment beyond variation
17226         ToNrEvent(currentMove-1);
17227         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17228         // kludge: use ParsePV() to append variation to game
17229         move = currentMove;
17230         ParsePV(start, TRUE, TRUE);
17231         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17232         ClearPremoveHighlights();
17233         CommentPopDown();
17234         ToNrEvent(currentMove+1);
17235 }
17236