Use Ctrl key in EditPosition mode to copy pieces
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152                          char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154                       char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226
227 #ifdef WIN32
228        extern void ConsoleCreate();
229 #endif
230
231 ChessProgramState *WhitePlayer();
232 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
233 int VerifyDisplayMode P(());
234
235 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
236 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
237 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
238 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
239 void ics_update_width P((int new_width));
240 extern char installDir[MSG_SIZ];
241 VariantClass startVariant; /* [HGM] nicks: initial variant */
242 Boolean abortMatch;
243
244 extern int tinyLayout, smallLayout;
245 ChessProgramStats programStats;
246 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
247 int endPV = -1;
248 static int exiting = 0; /* [HGM] moved to top */
249 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
250 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
251 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
252 int partnerHighlight[2];
253 Boolean partnerBoardValid = 0;
254 char partnerStatus[MSG_SIZ];
255 Boolean partnerUp;
256 Boolean originalFlip;
257 Boolean twoBoards = 0;
258 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
259 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
260 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
261 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
262 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
263 int opponentKibitzes;
264 int lastSavedGame; /* [HGM] save: ID of game */
265 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
266 extern int chatCount;
267 int chattingPartner;
268 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
269 char lastMsg[MSG_SIZ];
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
273
274 /* States for ics_getting_history */
275 #define H_FALSE 0
276 #define H_REQUESTED 1
277 #define H_GOT_REQ_HEADER 2
278 #define H_GOT_UNREQ_HEADER 3
279 #define H_GETTING_MOVES 4
280 #define H_GOT_UNWANTED_HEADER 5
281
282 /* whosays values for GameEnds */
283 #define GE_ICS 0
284 #define GE_ENGINE 1
285 #define GE_PLAYER 2
286 #define GE_FILE 3
287 #define GE_XBOARD 4
288 #define GE_ENGINE1 5
289 #define GE_ENGINE2 6
290
291 /* Maximum number of games in a cmail message */
292 #define CMAIL_MAX_GAMES 20
293
294 /* Different types of move when calling RegisterMove */
295 #define CMAIL_MOVE   0
296 #define CMAIL_RESIGN 1
297 #define CMAIL_DRAW   2
298 #define CMAIL_ACCEPT 3
299
300 /* Different types of result to remember for each game */
301 #define CMAIL_NOT_RESULT 0
302 #define CMAIL_OLD_RESULT 1
303 #define CMAIL_NEW_RESULT 2
304
305 /* Telnet protocol constants */
306 #define TN_WILL 0373
307 #define TN_WONT 0374
308 #define TN_DO   0375
309 #define TN_DONT 0376
310 #define TN_IAC  0377
311 #define TN_ECHO 0001
312 #define TN_SGA  0003
313 #define TN_PORT 23
314
315 char*
316 safeStrCpy (char *dst, const char *src, size_t count)
317 { // [HGM] made safe
318   int i;
319   assert( dst != NULL );
320   assert( src != NULL );
321   assert( count > 0 );
322
323   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
324   if(  i == count && dst[count-1] != NULLCHAR)
325     {
326       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
327       if(appData.debugMode)
328       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
329     }
330
331   return dst;
332 }
333
334 /* Some compiler can't cast u64 to double
335  * This function do the job for us:
336
337  * We use the highest bit for cast, this only
338  * works if the highest bit is not
339  * in use (This should not happen)
340  *
341  * We used this for all compiler
342  */
343 double
344 u64ToDouble (u64 value)
345 {
346   double r;
347   u64 tmp = value & u64Const(0x7fffffffffffffff);
348   r = (double)(s64)tmp;
349   if (value & u64Const(0x8000000000000000))
350        r +=  9.2233720368547758080e18; /* 2^63 */
351  return r;
352 }
353
354 /* Fake up flags for now, as we aren't keeping track of castling
355    availability yet. [HGM] Change of logic: the flag now only
356    indicates the type of castlings allowed by the rule of the game.
357    The actual rights themselves are maintained in the array
358    castlingRights, as part of the game history, and are not probed
359    by this function.
360  */
361 int
362 PosFlags (index)
363 {
364   int flags = F_ALL_CASTLE_OK;
365   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
366   switch (gameInfo.variant) {
367   case VariantSuicide:
368     flags &= ~F_ALL_CASTLE_OK;
369   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
370     flags |= F_IGNORE_CHECK;
371   case VariantLosers:
372     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
373     break;
374   case VariantAtomic:
375     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
376     break;
377   case VariantKriegspiel:
378     flags |= F_KRIEGSPIEL_CAPTURE;
379     break;
380   case VariantCapaRandom:
381   case VariantFischeRandom:
382     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
383   case VariantNoCastle:
384   case VariantShatranj:
385   case VariantCourier:
386   case VariantMakruk:
387   case VariantGrand:
388     flags &= ~F_ALL_CASTLE_OK;
389     break;
390   default:
391     break;
392   }
393   return flags;
394 }
395
396 FILE *gameFileFP, *debugFP, *serverFP;
397 char *currentDebugFile; // [HGM] debug split: to remember name
398
399 /*
400     [AS] Note: sometimes, the sscanf() function is used to parse the input
401     into a fixed-size buffer. Because of this, we must be prepared to
402     receive strings as long as the size of the input buffer, which is currently
403     set to 4K for Windows and 8K for the rest.
404     So, we must either allocate sufficiently large buffers here, or
405     reduce the size of the input buffer in the input reading part.
406 */
407
408 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
409 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
410 char thinkOutput1[MSG_SIZ*10];
411
412 ChessProgramState first, second, pairing;
413
414 /* premove variables */
415 int premoveToX = 0;
416 int premoveToY = 0;
417 int premoveFromX = 0;
418 int premoveFromY = 0;
419 int premovePromoChar = 0;
420 int gotPremove = 0;
421 Boolean alarmSounded;
422 /* end premove variables */
423
424 char *ics_prefix = "$";
425 int ics_type = ICS_GENERIC;
426
427 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
428 int pauseExamForwardMostMove = 0;
429 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
430 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
431 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
432 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
433 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
434 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
435 int whiteFlag = FALSE, blackFlag = FALSE;
436 int userOfferedDraw = FALSE;
437 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
438 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
439 int cmailMoveType[CMAIL_MAX_GAMES];
440 long ics_clock_paused = 0;
441 ProcRef icsPR = NoProc, cmailPR = NoProc;
442 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
443 GameMode gameMode = BeginningOfGame;
444 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
445 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
446 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
447 int hiddenThinkOutputState = 0; /* [AS] */
448 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
449 int adjudicateLossPlies = 6;
450 char white_holding[64], black_holding[64];
451 TimeMark lastNodeCountTime;
452 long lastNodeCount=0;
453 int shiftKey, controlKey; // [HGM] set by mouse handler
454
455 int have_sent_ICS_logon = 0;
456 int movesPerSession;
457 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
458 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
459 Boolean adjustedClock;
460 long timeControl_2; /* [AS] Allow separate time controls */
461 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
462 long timeRemaining[2][MAX_MOVES];
463 int matchGame = 0, nextGame = 0, roundNr = 0;
464 Boolean waitingForGame = FALSE;
465 TimeMark programStartTime, pauseStart;
466 char ics_handle[MSG_SIZ];
467 int have_set_title = 0;
468
469 /* animateTraining preserves the state of appData.animate
470  * when Training mode is activated. This allows the
471  * response to be animated when appData.animate == TRUE and
472  * appData.animateDragging == TRUE.
473  */
474 Boolean animateTraining;
475
476 GameInfo gameInfo;
477
478 AppData appData;
479
480 Board boards[MAX_MOVES];
481 /* [HGM] Following 7 needed for accurate legality tests: */
482 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
483 signed char  initialRights[BOARD_FILES];
484 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
485 int   initialRulePlies, FENrulePlies;
486 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
487 int loadFlag = 0;
488 Boolean shuffleOpenings;
489 int mute; // mute all sounds
490
491 // [HGM] vari: next 12 to save and restore variations
492 #define MAX_VARIATIONS 10
493 int framePtr = MAX_MOVES-1; // points to free stack entry
494 int storedGames = 0;
495 int savedFirst[MAX_VARIATIONS];
496 int savedLast[MAX_VARIATIONS];
497 int savedFramePtr[MAX_VARIATIONS];
498 char *savedDetails[MAX_VARIATIONS];
499 ChessMove savedResult[MAX_VARIATIONS];
500
501 void PushTail P((int firstMove, int lastMove));
502 Boolean PopTail P((Boolean annotate));
503 void PushInner P((int firstMove, int lastMove));
504 void PopInner P((Boolean annotate));
505 void CleanupTail P((void));
506
507 ChessSquare  FIDEArray[2][BOARD_FILES] = {
508     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
509         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
510     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
511         BlackKing, BlackBishop, BlackKnight, BlackRook }
512 };
513
514 ChessSquare twoKingsArray[2][BOARD_FILES] = {
515     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518         BlackKing, BlackKing, BlackKnight, BlackRook }
519 };
520
521 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
522     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
523         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
524     { BlackRook, BlackMan, BlackBishop, BlackQueen,
525         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
526 };
527
528 ChessSquare SpartanArray[2][BOARD_FILES] = {
529     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
530         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
531     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
532         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
533 };
534
535 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
536     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
539         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
540 };
541
542 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
543     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
544         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
545     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
546         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
547 };
548
549 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
551         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackMan, BlackFerz,
553         BlackKing, BlackMan, BlackKnight, BlackRook }
554 };
555
556
557 #if (BOARD_FILES>=10)
558 ChessSquare ShogiArray[2][BOARD_FILES] = {
559     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
560         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
561     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
562         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
563 };
564
565 ChessSquare XiangqiArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
567         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
568     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
569         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
570 };
571
572 ChessSquare CapablancaArray[2][BOARD_FILES] = {
573     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
574         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
575     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
576         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
577 };
578
579 ChessSquare GreatArray[2][BOARD_FILES] = {
580     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
581         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
582     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
583         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
584 };
585
586 ChessSquare JanusArray[2][BOARD_FILES] = {
587     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
588         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
589     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
590         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
591 };
592
593 ChessSquare GrandArray[2][BOARD_FILES] = {
594     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
595         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
596     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
597         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
598 };
599
600 #ifdef GOTHIC
601 ChessSquare GothicArray[2][BOARD_FILES] = {
602     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
603         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
604     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
605         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
606 };
607 #else // !GOTHIC
608 #define GothicArray CapablancaArray
609 #endif // !GOTHIC
610
611 #ifdef FALCON
612 ChessSquare FalconArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
614         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
616         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
617 };
618 #else // !FALCON
619 #define FalconArray CapablancaArray
620 #endif // !FALCON
621
622 #else // !(BOARD_FILES>=10)
623 #define XiangqiPosition FIDEArray
624 #define CapablancaArray FIDEArray
625 #define GothicArray FIDEArray
626 #define GreatArray FIDEArray
627 #endif // !(BOARD_FILES>=10)
628
629 #if (BOARD_FILES>=12)
630 ChessSquare CourierArray[2][BOARD_FILES] = {
631     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
632         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
633     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
634         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
635 };
636 #else // !(BOARD_FILES>=12)
637 #define CourierArray CapablancaArray
638 #endif // !(BOARD_FILES>=12)
639
640
641 Board initialPosition;
642
643
644 /* Convert str to a rating. Checks for special cases of "----",
645
646    "++++", etc. Also strips ()'s */
647 int
648 string_to_rating (char *str)
649 {
650   while(*str && !isdigit(*str)) ++str;
651   if (!*str)
652     return 0;   /* One of the special "no rating" cases */
653   else
654     return atoi(str);
655 }
656
657 void
658 ClearProgramStats ()
659 {
660     /* Init programStats */
661     programStats.movelist[0] = 0;
662     programStats.depth = 0;
663     programStats.nr_moves = 0;
664     programStats.moves_left = 0;
665     programStats.nodes = 0;
666     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
667     programStats.score = 0;
668     programStats.got_only_move = 0;
669     programStats.got_fail = 0;
670     programStats.line_is_book = 0;
671 }
672
673 void
674 CommonEngineInit ()
675 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
676     if (appData.firstPlaysBlack) {
677         first.twoMachinesColor = "black\n";
678         second.twoMachinesColor = "white\n";
679     } else {
680         first.twoMachinesColor = "white\n";
681         second.twoMachinesColor = "black\n";
682     }
683
684     first.other = &second;
685     second.other = &first;
686
687     { float norm = 1;
688         if(appData.timeOddsMode) {
689             norm = appData.timeOdds[0];
690             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
691         }
692         first.timeOdds  = appData.timeOdds[0]/norm;
693         second.timeOdds = appData.timeOdds[1]/norm;
694     }
695
696     if(programVersion) free(programVersion);
697     if (appData.noChessProgram) {
698         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
699         sprintf(programVersion, "%s", PACKAGE_STRING);
700     } else {
701       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
702       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
703       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
704     }
705 }
706
707 void
708 UnloadEngine (ChessProgramState *cps)
709 {
710         /* Kill off first chess program */
711         if (cps->isr != NULL)
712           RemoveInputSource(cps->isr);
713         cps->isr = NULL;
714
715         if (cps->pr != NoProc) {
716             ExitAnalyzeMode();
717             DoSleep( appData.delayBeforeQuit );
718             SendToProgram("quit\n", cps);
719             DoSleep( appData.delayAfterQuit );
720             DestroyChildProcess(cps->pr, cps->useSigterm);
721         }
722         cps->pr = NoProc;
723         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
724 }
725
726 void
727 ClearOptions (ChessProgramState *cps)
728 {
729     int i;
730     cps->nrOptions = cps->comboCnt = 0;
731     for(i=0; i<MAX_OPTIONS; i++) {
732         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
733         cps->option[i].textValue = 0;
734     }
735 }
736
737 char *engineNames[] = {
738   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
739      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
740 N_("first"),
741   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
742      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
743 N_("second")
744 };
745
746 void
747 InitEngine (ChessProgramState *cps, int n)
748 {   // [HGM] all engine initialiation put in a function that does one engine
749
750     ClearOptions(cps);
751
752     cps->which = engineNames[n];
753     cps->maybeThinking = FALSE;
754     cps->pr = NoProc;
755     cps->isr = NULL;
756     cps->sendTime = 2;
757     cps->sendDrawOffers = 1;
758
759     cps->program = appData.chessProgram[n];
760     cps->host = appData.host[n];
761     cps->dir = appData.directory[n];
762     cps->initString = appData.engInitString[n];
763     cps->computerString = appData.computerString[n];
764     cps->useSigint  = TRUE;
765     cps->useSigterm = TRUE;
766     cps->reuse = appData.reuse[n];
767     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
768     cps->useSetboard = FALSE;
769     cps->useSAN = FALSE;
770     cps->usePing = FALSE;
771     cps->lastPing = 0;
772     cps->lastPong = 0;
773     cps->usePlayother = FALSE;
774     cps->useColors = TRUE;
775     cps->useUsermove = FALSE;
776     cps->sendICS = FALSE;
777     cps->sendName = appData.icsActive;
778     cps->sdKludge = FALSE;
779     cps->stKludge = FALSE;
780     TidyProgramName(cps->program, cps->host, cps->tidy);
781     cps->matchWins = 0;
782     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
783     cps->analysisSupport = 2; /* detect */
784     cps->analyzing = FALSE;
785     cps->initDone = FALSE;
786
787     /* New features added by Tord: */
788     cps->useFEN960 = FALSE;
789     cps->useOOCastle = TRUE;
790     /* End of new features added by Tord. */
791     cps->fenOverride  = appData.fenOverride[n];
792
793     /* [HGM] time odds: set factor for each machine */
794     cps->timeOdds  = appData.timeOdds[n];
795
796     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
797     cps->accumulateTC = appData.accumulateTC[n];
798     cps->maxNrOfSessions = 1;
799
800     /* [HGM] debug */
801     cps->debug = FALSE;
802
803     cps->supportsNPS = UNKNOWN;
804     cps->memSize = FALSE;
805     cps->maxCores = FALSE;
806     cps->egtFormats[0] = NULLCHAR;
807
808     /* [HGM] options */
809     cps->optionSettings  = appData.engOptions[n];
810
811     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
812     cps->isUCI = appData.isUCI[n]; /* [AS] */
813     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
814
815     if (appData.protocolVersion[n] > PROTOVER
816         || appData.protocolVersion[n] < 1)
817       {
818         char buf[MSG_SIZ];
819         int len;
820
821         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
822                        appData.protocolVersion[n]);
823         if( (len >= MSG_SIZ) && appData.debugMode )
824           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
825
826         DisplayFatalError(buf, 0, 2);
827       }
828     else
829       {
830         cps->protocolVersion = appData.protocolVersion[n];
831       }
832
833     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
834     ParseFeatures(appData.featureDefaults, cps);
835 }
836
837 ChessProgramState *savCps;
838
839 void
840 LoadEngine ()
841 {
842     int i;
843     if(WaitForEngine(savCps, LoadEngine)) return;
844     CommonEngineInit(); // recalculate time odds
845     if(gameInfo.variant != StringToVariant(appData.variant)) {
846         // we changed variant when loading the engine; this forces us to reset
847         Reset(TRUE, savCps != &first);
848         EditGameEvent(); // for consistency with other path, as Reset changes mode
849     }
850     InitChessProgram(savCps, FALSE);
851     SendToProgram("force\n", savCps);
852     DisplayMessage("", "");
853     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
854     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
855     ThawUI();
856     SetGNUMode();
857 }
858
859 void
860 ReplaceEngine (ChessProgramState *cps, int n)
861 {
862     EditGameEvent();
863     UnloadEngine(cps);
864     appData.noChessProgram = FALSE;
865     appData.clockMode = TRUE;
866     InitEngine(cps, n);
867     UpdateLogos(TRUE);
868     if(n) return; // only startup first engine immediately; second can wait
869     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
870     LoadEngine();
871 }
872
873 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
874 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
875
876 static char resetOptions[] = 
877         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
878         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
879         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
880         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
881
882 void
883 FloatToFront(char **list, char *engineLine)
884 {
885     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
886     int i=0;
887     if(appData.recentEngines <= 0) return;
888     TidyProgramName(engineLine, "localhost", tidy+1);
889     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
890     strncpy(buf+1, *list, MSG_SIZ-50);
891     if(p = strstr(buf, tidy)) { // tidy name appears in list
892         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
893         while(*p++ = *++q); // squeeze out
894     }
895     strcat(tidy, buf+1); // put list behind tidy name
896     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
897     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
898     ASSIGN(*list, tidy+1);
899 }
900
901 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
902
903 void
904 Load (ChessProgramState *cps, int i)
905 {
906     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
907     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
908         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
909         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
910         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
911         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
912         appData.firstProtocolVersion = PROTOVER;
913         ParseArgsFromString(buf);
914         SwapEngines(i);
915         ReplaceEngine(cps, i);
916         FloatToFront(&appData.recentEngineList, engineLine);
917         return;
918     }
919     p = engineName;
920     while(q = strchr(p, SLASH)) p = q+1;
921     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
922     if(engineDir[0] != NULLCHAR) {
923         ASSIGN(appData.directory[i], engineDir);
924     } else if(p != engineName) { // derive directory from engine path, when not given
925         p[-1] = 0;
926         ASSIGN(appData.directory[i], engineName);
927         p[-1] = SLASH;
928         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
929     } else { ASSIGN(appData.directory[i], "."); }
930     if(params[0]) {
931         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
932         snprintf(command, MSG_SIZ, "%s %s", p, params);
933         p = command;
934     }
935     ASSIGN(appData.chessProgram[i], p);
936     appData.isUCI[i] = isUCI;
937     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
938     appData.hasOwnBookUCI[i] = hasBook;
939     if(!nickName[0]) useNick = FALSE;
940     if(useNick) ASSIGN(appData.pgnName[i], nickName);
941     if(addToList) {
942         int len;
943         char quote;
944         q = firstChessProgramNames;
945         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
946         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
947         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
948                         quote, p, quote, appData.directory[i], 
949                         useNick ? " -fn \"" : "",
950                         useNick ? nickName : "",
951                         useNick ? "\"" : "",
952                         v1 ? " -firstProtocolVersion 1" : "",
953                         hasBook ? "" : " -fNoOwnBookUCI",
954                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
955                         storeVariant ? " -variant " : "",
956                         storeVariant ? VariantName(gameInfo.variant) : "");
957         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
958         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
959         if(insert != q) insert[-1] = NULLCHAR;
960         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
961         if(q)   free(q);
962         FloatToFront(&appData.recentEngineList, buf);
963     }
964     ReplaceEngine(cps, i);
965 }
966
967 void
968 InitTimeControls ()
969 {
970     int matched, min, sec;
971     /*
972      * Parse timeControl resource
973      */
974     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
975                           appData.movesPerSession)) {
976         char buf[MSG_SIZ];
977         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
978         DisplayFatalError(buf, 0, 2);
979     }
980
981     /*
982      * Parse searchTime resource
983      */
984     if (*appData.searchTime != NULLCHAR) {
985         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
986         if (matched == 1) {
987             searchTime = min * 60;
988         } else if (matched == 2) {
989             searchTime = min * 60 + sec;
990         } else {
991             char buf[MSG_SIZ];
992             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
993             DisplayFatalError(buf, 0, 2);
994         }
995     }
996 }
997
998 void
999 InitBackEnd1 ()
1000 {
1001
1002     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1003     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1004
1005     GetTimeMark(&programStartTime);
1006     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1007     appData.seedBase = random() + (random()<<15);
1008     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1009
1010     ClearProgramStats();
1011     programStats.ok_to_send = 1;
1012     programStats.seen_stat = 0;
1013
1014     /*
1015      * Initialize game list
1016      */
1017     ListNew(&gameList);
1018
1019
1020     /*
1021      * Internet chess server status
1022      */
1023     if (appData.icsActive) {
1024         appData.matchMode = FALSE;
1025         appData.matchGames = 0;
1026 #if ZIPPY
1027         appData.noChessProgram = !appData.zippyPlay;
1028 #else
1029         appData.zippyPlay = FALSE;
1030         appData.zippyTalk = FALSE;
1031         appData.noChessProgram = TRUE;
1032 #endif
1033         if (*appData.icsHelper != NULLCHAR) {
1034             appData.useTelnet = TRUE;
1035             appData.telnetProgram = appData.icsHelper;
1036         }
1037     } else {
1038         appData.zippyTalk = appData.zippyPlay = FALSE;
1039     }
1040
1041     /* [AS] Initialize pv info list [HGM] and game state */
1042     {
1043         int i, j;
1044
1045         for( i=0; i<=framePtr; i++ ) {
1046             pvInfoList[i].depth = -1;
1047             boards[i][EP_STATUS] = EP_NONE;
1048             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1049         }
1050     }
1051
1052     InitTimeControls();
1053
1054     /* [AS] Adjudication threshold */
1055     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1056
1057     InitEngine(&first, 0);
1058     InitEngine(&second, 1);
1059     CommonEngineInit();
1060
1061     pairing.which = "pairing"; // pairing engine
1062     pairing.pr = NoProc;
1063     pairing.isr = NULL;
1064     pairing.program = appData.pairingEngine;
1065     pairing.host = "localhost";
1066     pairing.dir = ".";
1067
1068     if (appData.icsActive) {
1069         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1070     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1071         appData.clockMode = FALSE;
1072         first.sendTime = second.sendTime = 0;
1073     }
1074
1075 #if ZIPPY
1076     /* Override some settings from environment variables, for backward
1077        compatibility.  Unfortunately it's not feasible to have the env
1078        vars just set defaults, at least in xboard.  Ugh.
1079     */
1080     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1081       ZippyInit();
1082     }
1083 #endif
1084
1085     if (!appData.icsActive) {
1086       char buf[MSG_SIZ];
1087       int len;
1088
1089       /* Check for variants that are supported only in ICS mode,
1090          or not at all.  Some that are accepted here nevertheless
1091          have bugs; see comments below.
1092       */
1093       VariantClass variant = StringToVariant(appData.variant);
1094       switch (variant) {
1095       case VariantBughouse:     /* need four players and two boards */
1096       case VariantKriegspiel:   /* need to hide pieces and move details */
1097         /* case VariantFischeRandom: (Fabien: moved below) */
1098         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1099         if( (len >= MSG_SIZ) && appData.debugMode )
1100           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1101
1102         DisplayFatalError(buf, 0, 2);
1103         return;
1104
1105       case VariantUnknown:
1106       case VariantLoadable:
1107       case Variant29:
1108       case Variant30:
1109       case Variant31:
1110       case Variant32:
1111       case Variant33:
1112       case Variant34:
1113       case Variant35:
1114       case Variant36:
1115       default:
1116         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1117         if( (len >= MSG_SIZ) && appData.debugMode )
1118           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1119
1120         DisplayFatalError(buf, 0, 2);
1121         return;
1122
1123       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1124       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1125       case VariantGothic:     /* [HGM] should work */
1126       case VariantCapablanca: /* [HGM] should work */
1127       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1128       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1129       case VariantKnightmate: /* [HGM] should work */
1130       case VariantCylinder:   /* [HGM] untested */
1131       case VariantFalcon:     /* [HGM] untested */
1132       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1133                                  offboard interposition not understood */
1134       case VariantNormal:     /* definitely works! */
1135       case VariantWildCastle: /* pieces not automatically shuffled */
1136       case VariantNoCastle:   /* pieces not automatically shuffled */
1137       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1138       case VariantLosers:     /* should work except for win condition,
1139                                  and doesn't know captures are mandatory */
1140       case VariantSuicide:    /* should work except for win condition,
1141                                  and doesn't know captures are mandatory */
1142       case VariantGiveaway:   /* should work except for win condition,
1143                                  and doesn't know captures are mandatory */
1144       case VariantTwoKings:   /* should work */
1145       case VariantAtomic:     /* should work except for win condition */
1146       case Variant3Check:     /* should work except for win condition */
1147       case VariantShatranj:   /* should work except for all win conditions */
1148       case VariantMakruk:     /* should work except for draw countdown */
1149       case VariantBerolina:   /* might work if TestLegality is off */
1150       case VariantCapaRandom: /* should work */
1151       case VariantJanus:      /* should work */
1152       case VariantSuper:      /* experimental */
1153       case VariantGreat:      /* experimental, requires legality testing to be off */
1154       case VariantSChess:     /* S-Chess, should work */
1155       case VariantGrand:      /* should work */
1156       case VariantSpartan:    /* should work */
1157         break;
1158       }
1159     }
1160
1161 }
1162
1163 int
1164 NextIntegerFromString (char ** str, long * value)
1165 {
1166     int result = -1;
1167     char * s = *str;
1168
1169     while( *s == ' ' || *s == '\t' ) {
1170         s++;
1171     }
1172
1173     *value = 0;
1174
1175     if( *s >= '0' && *s <= '9' ) {
1176         while( *s >= '0' && *s <= '9' ) {
1177             *value = *value * 10 + (*s - '0');
1178             s++;
1179         }
1180
1181         result = 0;
1182     }
1183
1184     *str = s;
1185
1186     return result;
1187 }
1188
1189 int
1190 NextTimeControlFromString (char ** str, long * value)
1191 {
1192     long temp;
1193     int result = NextIntegerFromString( str, &temp );
1194
1195     if( result == 0 ) {
1196         *value = temp * 60; /* Minutes */
1197         if( **str == ':' ) {
1198             (*str)++;
1199             result = NextIntegerFromString( str, &temp );
1200             *value += temp; /* Seconds */
1201         }
1202     }
1203
1204     return result;
1205 }
1206
1207 int
1208 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1209 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1210     int result = -1, type = 0; long temp, temp2;
1211
1212     if(**str != ':') return -1; // old params remain in force!
1213     (*str)++;
1214     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1215     if( NextIntegerFromString( str, &temp ) ) return -1;
1216     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1217
1218     if(**str != '/') {
1219         /* time only: incremental or sudden-death time control */
1220         if(**str == '+') { /* increment follows; read it */
1221             (*str)++;
1222             if(**str == '!') type = *(*str)++; // Bronstein TC
1223             if(result = NextIntegerFromString( str, &temp2)) return -1;
1224             *inc = temp2 * 1000;
1225             if(**str == '.') { // read fraction of increment
1226                 char *start = ++(*str);
1227                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1228                 temp2 *= 1000;
1229                 while(start++ < *str) temp2 /= 10;
1230                 *inc += temp2;
1231             }
1232         } else *inc = 0;
1233         *moves = 0; *tc = temp * 1000; *incType = type;
1234         return 0;
1235     }
1236
1237     (*str)++; /* classical time control */
1238     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1239
1240     if(result == 0) {
1241         *moves = temp;
1242         *tc    = temp2 * 1000;
1243         *inc   = 0;
1244         *incType = type;
1245     }
1246     return result;
1247 }
1248
1249 int
1250 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1251 {   /* [HGM] get time to add from the multi-session time-control string */
1252     int incType, moves=1; /* kludge to force reading of first session */
1253     long time, increment;
1254     char *s = tcString;
1255
1256     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1257     do {
1258         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1259         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1260         if(movenr == -1) return time;    /* last move before new session     */
1261         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1262         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1263         if(!moves) return increment;     /* current session is incremental   */
1264         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1265     } while(movenr >= -1);               /* try again for next session       */
1266
1267     return 0; // no new time quota on this move
1268 }
1269
1270 int
1271 ParseTimeControl (char *tc, float ti, int mps)
1272 {
1273   long tc1;
1274   long tc2;
1275   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1276   int min, sec=0;
1277
1278   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1279   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1280       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1281   if(ti > 0) {
1282
1283     if(mps)
1284       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1285     else 
1286       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1287   } else {
1288     if(mps)
1289       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1290     else 
1291       snprintf(buf, MSG_SIZ, ":%s", mytc);
1292   }
1293   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1294   
1295   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1296     return FALSE;
1297   }
1298
1299   if( *tc == '/' ) {
1300     /* Parse second time control */
1301     tc++;
1302
1303     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1304       return FALSE;
1305     }
1306
1307     if( tc2 == 0 ) {
1308       return FALSE;
1309     }
1310
1311     timeControl_2 = tc2 * 1000;
1312   }
1313   else {
1314     timeControl_2 = 0;
1315   }
1316
1317   if( tc1 == 0 ) {
1318     return FALSE;
1319   }
1320
1321   timeControl = tc1 * 1000;
1322
1323   if (ti >= 0) {
1324     timeIncrement = ti * 1000;  /* convert to ms */
1325     movesPerSession = 0;
1326   } else {
1327     timeIncrement = 0;
1328     movesPerSession = mps;
1329   }
1330   return TRUE;
1331 }
1332
1333 void
1334 InitBackEnd2 ()
1335 {
1336     if (appData.debugMode) {
1337         fprintf(debugFP, "%s\n", programVersion);
1338     }
1339     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1340
1341     set_cont_sequence(appData.wrapContSeq);
1342     if (appData.matchGames > 0) {
1343         appData.matchMode = TRUE;
1344     } else if (appData.matchMode) {
1345         appData.matchGames = 1;
1346     }
1347     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1348         appData.matchGames = appData.sameColorGames;
1349     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1350         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1351         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1352     }
1353     Reset(TRUE, FALSE);
1354     if (appData.noChessProgram || first.protocolVersion == 1) {
1355       InitBackEnd3();
1356     } else {
1357       /* kludge: allow timeout for initial "feature" commands */
1358       FreezeUI();
1359       DisplayMessage("", _("Starting chess program"));
1360       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1361     }
1362 }
1363
1364 int
1365 CalculateIndex (int index, int gameNr)
1366 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1367     int res;
1368     if(index > 0) return index; // fixed nmber
1369     if(index == 0) return 1;
1370     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1371     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1372     return res;
1373 }
1374
1375 int
1376 LoadGameOrPosition (int gameNr)
1377 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1378     if (*appData.loadGameFile != NULLCHAR) {
1379         if (!LoadGameFromFile(appData.loadGameFile,
1380                 CalculateIndex(appData.loadGameIndex, gameNr),
1381                               appData.loadGameFile, FALSE)) {
1382             DisplayFatalError(_("Bad game file"), 0, 1);
1383             return 0;
1384         }
1385     } else if (*appData.loadPositionFile != NULLCHAR) {
1386         if (!LoadPositionFromFile(appData.loadPositionFile,
1387                 CalculateIndex(appData.loadPositionIndex, gameNr),
1388                                   appData.loadPositionFile)) {
1389             DisplayFatalError(_("Bad position file"), 0, 1);
1390             return 0;
1391         }
1392     }
1393     return 1;
1394 }
1395
1396 void
1397 ReserveGame (int gameNr, char resChar)
1398 {
1399     FILE *tf = fopen(appData.tourneyFile, "r+");
1400     char *p, *q, c, buf[MSG_SIZ];
1401     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1402     safeStrCpy(buf, lastMsg, MSG_SIZ);
1403     DisplayMessage(_("Pick new game"), "");
1404     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1405     ParseArgsFromFile(tf);
1406     p = q = appData.results;
1407     if(appData.debugMode) {
1408       char *r = appData.participants;
1409       fprintf(debugFP, "results = '%s'\n", p);
1410       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1411       fprintf(debugFP, "\n");
1412     }
1413     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1414     nextGame = q - p;
1415     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1416     safeStrCpy(q, p, strlen(p) + 2);
1417     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1418     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1419     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1420         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1421         q[nextGame] = '*';
1422     }
1423     fseek(tf, -(strlen(p)+4), SEEK_END);
1424     c = fgetc(tf);
1425     if(c != '"') // depending on DOS or Unix line endings we can be one off
1426          fseek(tf, -(strlen(p)+2), SEEK_END);
1427     else fseek(tf, -(strlen(p)+3), SEEK_END);
1428     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1429     DisplayMessage(buf, "");
1430     free(p); appData.results = q;
1431     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1432        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1433       int round = appData.defaultMatchGames * appData.tourneyType;
1434       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1435          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1436         UnloadEngine(&first);  // next game belongs to other pairing;
1437         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1438     }
1439     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1440 }
1441
1442 void
1443 MatchEvent (int mode)
1444 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1445         int dummy;
1446         if(matchMode) { // already in match mode: switch it off
1447             abortMatch = TRUE;
1448             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1449             return;
1450         }
1451 //      if(gameMode != BeginningOfGame) {
1452 //          DisplayError(_("You can only start a match from the initial position."), 0);
1453 //          return;
1454 //      }
1455         abortMatch = FALSE;
1456         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1457         /* Set up machine vs. machine match */
1458         nextGame = 0;
1459         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1460         if(appData.tourneyFile[0]) {
1461             ReserveGame(-1, 0);
1462             if(nextGame > appData.matchGames) {
1463                 char buf[MSG_SIZ];
1464                 if(strchr(appData.results, '*') == NULL) {
1465                     FILE *f;
1466                     appData.tourneyCycles++;
1467                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1468                         fclose(f);
1469                         NextTourneyGame(-1, &dummy);
1470                         ReserveGame(-1, 0);
1471                         if(nextGame <= appData.matchGames) {
1472                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1473                             matchMode = mode;
1474                             ScheduleDelayedEvent(NextMatchGame, 10000);
1475                             return;
1476                         }
1477                     }
1478                 }
1479                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1480                 DisplayError(buf, 0);
1481                 appData.tourneyFile[0] = 0;
1482                 return;
1483             }
1484         } else
1485         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1486             DisplayFatalError(_("Can't have a match with no chess programs"),
1487                               0, 2);
1488             return;
1489         }
1490         matchMode = mode;
1491         matchGame = roundNr = 1;
1492         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1493         NextMatchGame();
1494 }
1495
1496 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1497
1498 void
1499 InitBackEnd3 P((void))
1500 {
1501     GameMode initialMode;
1502     char buf[MSG_SIZ];
1503     int err, len;
1504
1505     InitChessProgram(&first, startedFromSetupPosition);
1506
1507     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1508         free(programVersion);
1509         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1510         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1511         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1512     }
1513
1514     if (appData.icsActive) {
1515 #ifdef WIN32
1516         /* [DM] Make a console window if needed [HGM] merged ifs */
1517         ConsoleCreate();
1518 #endif
1519         err = establish();
1520         if (err != 0)
1521           {
1522             if (*appData.icsCommPort != NULLCHAR)
1523               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1524                              appData.icsCommPort);
1525             else
1526               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1527                         appData.icsHost, appData.icsPort);
1528
1529             if( (len >= MSG_SIZ) && appData.debugMode )
1530               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1531
1532             DisplayFatalError(buf, err, 1);
1533             return;
1534         }
1535         SetICSMode();
1536         telnetISR =
1537           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1538         fromUserISR =
1539           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1540         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1541             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1542     } else if (appData.noChessProgram) {
1543         SetNCPMode();
1544     } else {
1545         SetGNUMode();
1546     }
1547
1548     if (*appData.cmailGameName != NULLCHAR) {
1549         SetCmailMode();
1550         OpenLoopback(&cmailPR);
1551         cmailISR =
1552           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1553     }
1554
1555     ThawUI();
1556     DisplayMessage("", "");
1557     if (StrCaseCmp(appData.initialMode, "") == 0) {
1558       initialMode = BeginningOfGame;
1559       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1560         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1561         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1562         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1563         ModeHighlight();
1564       }
1565     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1566       initialMode = TwoMachinesPlay;
1567     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1568       initialMode = AnalyzeFile;
1569     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1570       initialMode = AnalyzeMode;
1571     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1572       initialMode = MachinePlaysWhite;
1573     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1574       initialMode = MachinePlaysBlack;
1575     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1576       initialMode = EditGame;
1577     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1578       initialMode = EditPosition;
1579     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1580       initialMode = Training;
1581     } else {
1582       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1583       if( (len >= MSG_SIZ) && appData.debugMode )
1584         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1585
1586       DisplayFatalError(buf, 0, 2);
1587       return;
1588     }
1589
1590     if (appData.matchMode) {
1591         if(appData.tourneyFile[0]) { // start tourney from command line
1592             FILE *f;
1593             if(f = fopen(appData.tourneyFile, "r")) {
1594                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1595                 fclose(f);
1596                 appData.clockMode = TRUE;
1597                 SetGNUMode();
1598             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1599         }
1600         MatchEvent(TRUE);
1601     } else if (*appData.cmailGameName != NULLCHAR) {
1602         /* Set up cmail mode */
1603         ReloadCmailMsgEvent(TRUE);
1604     } else {
1605         /* Set up other modes */
1606         if (initialMode == AnalyzeFile) {
1607           if (*appData.loadGameFile == NULLCHAR) {
1608             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1609             return;
1610           }
1611         }
1612         if (*appData.loadGameFile != NULLCHAR) {
1613             (void) LoadGameFromFile(appData.loadGameFile,
1614                                     appData.loadGameIndex,
1615                                     appData.loadGameFile, TRUE);
1616         } else if (*appData.loadPositionFile != NULLCHAR) {
1617             (void) LoadPositionFromFile(appData.loadPositionFile,
1618                                         appData.loadPositionIndex,
1619                                         appData.loadPositionFile);
1620             /* [HGM] try to make self-starting even after FEN load */
1621             /* to allow automatic setup of fairy variants with wtm */
1622             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1623                 gameMode = BeginningOfGame;
1624                 setboardSpoiledMachineBlack = 1;
1625             }
1626             /* [HGM] loadPos: make that every new game uses the setup */
1627             /* from file as long as we do not switch variant          */
1628             if(!blackPlaysFirst) {
1629                 startedFromPositionFile = TRUE;
1630                 CopyBoard(filePosition, boards[0]);
1631             }
1632         }
1633         if (initialMode == AnalyzeMode) {
1634           if (appData.noChessProgram) {
1635             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1636             return;
1637           }
1638           if (appData.icsActive) {
1639             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1640             return;
1641           }
1642           AnalyzeModeEvent();
1643         } else if (initialMode == AnalyzeFile) {
1644           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1645           ShowThinkingEvent();
1646           AnalyzeFileEvent();
1647           AnalysisPeriodicEvent(1);
1648         } else if (initialMode == MachinePlaysWhite) {
1649           if (appData.noChessProgram) {
1650             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1651                               0, 2);
1652             return;
1653           }
1654           if (appData.icsActive) {
1655             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1656                               0, 2);
1657             return;
1658           }
1659           MachineWhiteEvent();
1660         } else if (initialMode == MachinePlaysBlack) {
1661           if (appData.noChessProgram) {
1662             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1663                               0, 2);
1664             return;
1665           }
1666           if (appData.icsActive) {
1667             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1668                               0, 2);
1669             return;
1670           }
1671           MachineBlackEvent();
1672         } else if (initialMode == TwoMachinesPlay) {
1673           if (appData.noChessProgram) {
1674             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1675                               0, 2);
1676             return;
1677           }
1678           if (appData.icsActive) {
1679             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1680                               0, 2);
1681             return;
1682           }
1683           TwoMachinesEvent();
1684         } else if (initialMode == EditGame) {
1685           EditGameEvent();
1686         } else if (initialMode == EditPosition) {
1687           EditPositionEvent();
1688         } else if (initialMode == Training) {
1689           if (*appData.loadGameFile == NULLCHAR) {
1690             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1691             return;
1692           }
1693           TrainingEvent();
1694         }
1695     }
1696 }
1697
1698 void
1699 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1700 {
1701     DisplayBook(current+1);
1702
1703     MoveHistorySet( movelist, first, last, current, pvInfoList );
1704
1705     EvalGraphSet( first, last, current, pvInfoList );
1706
1707     MakeEngineOutputTitle();
1708 }
1709
1710 /*
1711  * Establish will establish a contact to a remote host.port.
1712  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1713  *  used to talk to the host.
1714  * Returns 0 if okay, error code if not.
1715  */
1716 int
1717 establish ()
1718 {
1719     char buf[MSG_SIZ];
1720
1721     if (*appData.icsCommPort != NULLCHAR) {
1722         /* Talk to the host through a serial comm port */
1723         return OpenCommPort(appData.icsCommPort, &icsPR);
1724
1725     } else if (*appData.gateway != NULLCHAR) {
1726         if (*appData.remoteShell == NULLCHAR) {
1727             /* Use the rcmd protocol to run telnet program on a gateway host */
1728             snprintf(buf, sizeof(buf), "%s %s %s",
1729                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1730             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1731
1732         } else {
1733             /* Use the rsh program to run telnet program on a gateway host */
1734             if (*appData.remoteUser == NULLCHAR) {
1735                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1736                         appData.gateway, appData.telnetProgram,
1737                         appData.icsHost, appData.icsPort);
1738             } else {
1739                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1740                         appData.remoteShell, appData.gateway,
1741                         appData.remoteUser, appData.telnetProgram,
1742                         appData.icsHost, appData.icsPort);
1743             }
1744             return StartChildProcess(buf, "", &icsPR);
1745
1746         }
1747     } else if (appData.useTelnet) {
1748         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1749
1750     } else {
1751         /* TCP socket interface differs somewhat between
1752            Unix and NT; handle details in the front end.
1753            */
1754         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1755     }
1756 }
1757
1758 void
1759 EscapeExpand (char *p, char *q)
1760 {       // [HGM] initstring: routine to shape up string arguments
1761         while(*p++ = *q++) if(p[-1] == '\\')
1762             switch(*q++) {
1763                 case 'n': p[-1] = '\n'; break;
1764                 case 'r': p[-1] = '\r'; break;
1765                 case 't': p[-1] = '\t'; break;
1766                 case '\\': p[-1] = '\\'; break;
1767                 case 0: *p = 0; return;
1768                 default: p[-1] = q[-1]; break;
1769             }
1770 }
1771
1772 void
1773 show_bytes (FILE *fp, char *buf, int count)
1774 {
1775     while (count--) {
1776         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1777             fprintf(fp, "\\%03o", *buf & 0xff);
1778         } else {
1779             putc(*buf, fp);
1780         }
1781         buf++;
1782     }
1783     fflush(fp);
1784 }
1785
1786 /* Returns an errno value */
1787 int
1788 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1789 {
1790     char buf[8192], *p, *q, *buflim;
1791     int left, newcount, outcount;
1792
1793     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1794         *appData.gateway != NULLCHAR) {
1795         if (appData.debugMode) {
1796             fprintf(debugFP, ">ICS: ");
1797             show_bytes(debugFP, message, count);
1798             fprintf(debugFP, "\n");
1799         }
1800         return OutputToProcess(pr, message, count, outError);
1801     }
1802
1803     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1804     p = message;
1805     q = buf;
1806     left = count;
1807     newcount = 0;
1808     while (left) {
1809         if (q >= buflim) {
1810             if (appData.debugMode) {
1811                 fprintf(debugFP, ">ICS: ");
1812                 show_bytes(debugFP, buf, newcount);
1813                 fprintf(debugFP, "\n");
1814             }
1815             outcount = OutputToProcess(pr, buf, newcount, outError);
1816             if (outcount < newcount) return -1; /* to be sure */
1817             q = buf;
1818             newcount = 0;
1819         }
1820         if (*p == '\n') {
1821             *q++ = '\r';
1822             newcount++;
1823         } else if (((unsigned char) *p) == TN_IAC) {
1824             *q++ = (char) TN_IAC;
1825             newcount ++;
1826         }
1827         *q++ = *p++;
1828         newcount++;
1829         left--;
1830     }
1831     if (appData.debugMode) {
1832         fprintf(debugFP, ">ICS: ");
1833         show_bytes(debugFP, buf, newcount);
1834         fprintf(debugFP, "\n");
1835     }
1836     outcount = OutputToProcess(pr, buf, newcount, outError);
1837     if (outcount < newcount) return -1; /* to be sure */
1838     return count;
1839 }
1840
1841 void
1842 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1843 {
1844     int outError, outCount;
1845     static int gotEof = 0;
1846
1847     /* Pass data read from player on to ICS */
1848     if (count > 0) {
1849         gotEof = 0;
1850         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1851         if (outCount < count) {
1852             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1853         }
1854     } else if (count < 0) {
1855         RemoveInputSource(isr);
1856         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1857     } else if (gotEof++ > 0) {
1858         RemoveInputSource(isr);
1859         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1860     }
1861 }
1862
1863 void
1864 KeepAlive ()
1865 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1866     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1867     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1868     SendToICS("date\n");
1869     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1870 }
1871
1872 /* added routine for printf style output to ics */
1873 void
1874 ics_printf (char *format, ...)
1875 {
1876     char buffer[MSG_SIZ];
1877     va_list args;
1878
1879     va_start(args, format);
1880     vsnprintf(buffer, sizeof(buffer), format, args);
1881     buffer[sizeof(buffer)-1] = '\0';
1882     SendToICS(buffer);
1883     va_end(args);
1884 }
1885
1886 void
1887 SendToICS (char *s)
1888 {
1889     int count, outCount, outError;
1890
1891     if (icsPR == NoProc) return;
1892
1893     count = strlen(s);
1894     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1895     if (outCount < count) {
1896         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1897     }
1898 }
1899
1900 /* This is used for sending logon scripts to the ICS. Sending
1901    without a delay causes problems when using timestamp on ICC
1902    (at least on my machine). */
1903 void
1904 SendToICSDelayed (char *s, long msdelay)
1905 {
1906     int count, outCount, outError;
1907
1908     if (icsPR == NoProc) return;
1909
1910     count = strlen(s);
1911     if (appData.debugMode) {
1912         fprintf(debugFP, ">ICS: ");
1913         show_bytes(debugFP, s, count);
1914         fprintf(debugFP, "\n");
1915     }
1916     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1917                                       msdelay);
1918     if (outCount < count) {
1919         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1920     }
1921 }
1922
1923
1924 /* Remove all highlighting escape sequences in s
1925    Also deletes any suffix starting with '('
1926    */
1927 char *
1928 StripHighlightAndTitle (char *s)
1929 {
1930     static char retbuf[MSG_SIZ];
1931     char *p = retbuf;
1932
1933     while (*s != NULLCHAR) {
1934         while (*s == '\033') {
1935             while (*s != NULLCHAR && !isalpha(*s)) s++;
1936             if (*s != NULLCHAR) s++;
1937         }
1938         while (*s != NULLCHAR && *s != '\033') {
1939             if (*s == '(' || *s == '[') {
1940                 *p = NULLCHAR;
1941                 return retbuf;
1942             }
1943             *p++ = *s++;
1944         }
1945     }
1946     *p = NULLCHAR;
1947     return retbuf;
1948 }
1949
1950 /* Remove all highlighting escape sequences in s */
1951 char *
1952 StripHighlight (char *s)
1953 {
1954     static char retbuf[MSG_SIZ];
1955     char *p = retbuf;
1956
1957     while (*s != NULLCHAR) {
1958         while (*s == '\033') {
1959             while (*s != NULLCHAR && !isalpha(*s)) s++;
1960             if (*s != NULLCHAR) s++;
1961         }
1962         while (*s != NULLCHAR && *s != '\033') {
1963             *p++ = *s++;
1964         }
1965     }
1966     *p = NULLCHAR;
1967     return retbuf;
1968 }
1969
1970 char *variantNames[] = VARIANT_NAMES;
1971 char *
1972 VariantName (VariantClass v)
1973 {
1974     return variantNames[v];
1975 }
1976
1977
1978 /* Identify a variant from the strings the chess servers use or the
1979    PGN Variant tag names we use. */
1980 VariantClass
1981 StringToVariant (char *e)
1982 {
1983     char *p;
1984     int wnum = -1;
1985     VariantClass v = VariantNormal;
1986     int i, found = FALSE;
1987     char buf[MSG_SIZ];
1988     int len;
1989
1990     if (!e) return v;
1991
1992     /* [HGM] skip over optional board-size prefixes */
1993     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1994         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1995         while( *e++ != '_');
1996     }
1997
1998     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1999         v = VariantNormal;
2000         found = TRUE;
2001     } else
2002     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2003       if (StrCaseStr(e, variantNames[i])) {
2004         v = (VariantClass) i;
2005         found = TRUE;
2006         break;
2007       }
2008     }
2009
2010     if (!found) {
2011       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2012           || StrCaseStr(e, "wild/fr")
2013           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2014         v = VariantFischeRandom;
2015       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2016                  (i = 1, p = StrCaseStr(e, "w"))) {
2017         p += i;
2018         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2019         if (isdigit(*p)) {
2020           wnum = atoi(p);
2021         } else {
2022           wnum = -1;
2023         }
2024         switch (wnum) {
2025         case 0: /* FICS only, actually */
2026         case 1:
2027           /* Castling legal even if K starts on d-file */
2028           v = VariantWildCastle;
2029           break;
2030         case 2:
2031         case 3:
2032         case 4:
2033           /* Castling illegal even if K & R happen to start in
2034              normal positions. */
2035           v = VariantNoCastle;
2036           break;
2037         case 5:
2038         case 7:
2039         case 8:
2040         case 10:
2041         case 11:
2042         case 12:
2043         case 13:
2044         case 14:
2045         case 15:
2046         case 18:
2047         case 19:
2048           /* Castling legal iff K & R start in normal positions */
2049           v = VariantNormal;
2050           break;
2051         case 6:
2052         case 20:
2053         case 21:
2054           /* Special wilds for position setup; unclear what to do here */
2055           v = VariantLoadable;
2056           break;
2057         case 9:
2058           /* Bizarre ICC game */
2059           v = VariantTwoKings;
2060           break;
2061         case 16:
2062           v = VariantKriegspiel;
2063           break;
2064         case 17:
2065           v = VariantLosers;
2066           break;
2067         case 22:
2068           v = VariantFischeRandom;
2069           break;
2070         case 23:
2071           v = VariantCrazyhouse;
2072           break;
2073         case 24:
2074           v = VariantBughouse;
2075           break;
2076         case 25:
2077           v = Variant3Check;
2078           break;
2079         case 26:
2080           /* Not quite the same as FICS suicide! */
2081           v = VariantGiveaway;
2082           break;
2083         case 27:
2084           v = VariantAtomic;
2085           break;
2086         case 28:
2087           v = VariantShatranj;
2088           break;
2089
2090         /* Temporary names for future ICC types.  The name *will* change in
2091            the next xboard/WinBoard release after ICC defines it. */
2092         case 29:
2093           v = Variant29;
2094           break;
2095         case 30:
2096           v = Variant30;
2097           break;
2098         case 31:
2099           v = Variant31;
2100           break;
2101         case 32:
2102           v = Variant32;
2103           break;
2104         case 33:
2105           v = Variant33;
2106           break;
2107         case 34:
2108           v = Variant34;
2109           break;
2110         case 35:
2111           v = Variant35;
2112           break;
2113         case 36:
2114           v = Variant36;
2115           break;
2116         case 37:
2117           v = VariantShogi;
2118           break;
2119         case 38:
2120           v = VariantXiangqi;
2121           break;
2122         case 39:
2123           v = VariantCourier;
2124           break;
2125         case 40:
2126           v = VariantGothic;
2127           break;
2128         case 41:
2129           v = VariantCapablanca;
2130           break;
2131         case 42:
2132           v = VariantKnightmate;
2133           break;
2134         case 43:
2135           v = VariantFairy;
2136           break;
2137         case 44:
2138           v = VariantCylinder;
2139           break;
2140         case 45:
2141           v = VariantFalcon;
2142           break;
2143         case 46:
2144           v = VariantCapaRandom;
2145           break;
2146         case 47:
2147           v = VariantBerolina;
2148           break;
2149         case 48:
2150           v = VariantJanus;
2151           break;
2152         case 49:
2153           v = VariantSuper;
2154           break;
2155         case 50:
2156           v = VariantGreat;
2157           break;
2158         case -1:
2159           /* Found "wild" or "w" in the string but no number;
2160              must assume it's normal chess. */
2161           v = VariantNormal;
2162           break;
2163         default:
2164           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2165           if( (len >= MSG_SIZ) && appData.debugMode )
2166             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2167
2168           DisplayError(buf, 0);
2169           v = VariantUnknown;
2170           break;
2171         }
2172       }
2173     }
2174     if (appData.debugMode) {
2175       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2176               e, wnum, VariantName(v));
2177     }
2178     return v;
2179 }
2180
2181 static int leftover_start = 0, leftover_len = 0;
2182 char star_match[STAR_MATCH_N][MSG_SIZ];
2183
2184 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2185    advance *index beyond it, and set leftover_start to the new value of
2186    *index; else return FALSE.  If pattern contains the character '*', it
2187    matches any sequence of characters not containing '\r', '\n', or the
2188    character following the '*' (if any), and the matched sequence(s) are
2189    copied into star_match.
2190    */
2191 int
2192 looking_at ( char *buf, int *index, char *pattern)
2193 {
2194     char *bufp = &buf[*index], *patternp = pattern;
2195     int star_count = 0;
2196     char *matchp = star_match[0];
2197
2198     for (;;) {
2199         if (*patternp == NULLCHAR) {
2200             *index = leftover_start = bufp - buf;
2201             *matchp = NULLCHAR;
2202             return TRUE;
2203         }
2204         if (*bufp == NULLCHAR) return FALSE;
2205         if (*patternp == '*') {
2206             if (*bufp == *(patternp + 1)) {
2207                 *matchp = NULLCHAR;
2208                 matchp = star_match[++star_count];
2209                 patternp += 2;
2210                 bufp++;
2211                 continue;
2212             } else if (*bufp == '\n' || *bufp == '\r') {
2213                 patternp++;
2214                 if (*patternp == NULLCHAR)
2215                   continue;
2216                 else
2217                   return FALSE;
2218             } else {
2219                 *matchp++ = *bufp++;
2220                 continue;
2221             }
2222         }
2223         if (*patternp != *bufp) return FALSE;
2224         patternp++;
2225         bufp++;
2226     }
2227 }
2228
2229 void
2230 SendToPlayer (char *data, int length)
2231 {
2232     int error, outCount;
2233     outCount = OutputToProcess(NoProc, data, length, &error);
2234     if (outCount < length) {
2235         DisplayFatalError(_("Error writing to display"), error, 1);
2236     }
2237 }
2238
2239 void
2240 PackHolding (char packed[], char *holding)
2241 {
2242     char *p = holding;
2243     char *q = packed;
2244     int runlength = 0;
2245     int curr = 9999;
2246     do {
2247         if (*p == curr) {
2248             runlength++;
2249         } else {
2250             switch (runlength) {
2251               case 0:
2252                 break;
2253               case 1:
2254                 *q++ = curr;
2255                 break;
2256               case 2:
2257                 *q++ = curr;
2258                 *q++ = curr;
2259                 break;
2260               default:
2261                 sprintf(q, "%d", runlength);
2262                 while (*q) q++;
2263                 *q++ = curr;
2264                 break;
2265             }
2266             runlength = 1;
2267             curr = *p;
2268         }
2269     } while (*p++);
2270     *q = NULLCHAR;
2271 }
2272
2273 /* Telnet protocol requests from the front end */
2274 void
2275 TelnetRequest (unsigned char ddww, unsigned char option)
2276 {
2277     unsigned char msg[3];
2278     int outCount, outError;
2279
2280     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2281
2282     if (appData.debugMode) {
2283         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2284         switch (ddww) {
2285           case TN_DO:
2286             ddwwStr = "DO";
2287             break;
2288           case TN_DONT:
2289             ddwwStr = "DONT";
2290             break;
2291           case TN_WILL:
2292             ddwwStr = "WILL";
2293             break;
2294           case TN_WONT:
2295             ddwwStr = "WONT";
2296             break;
2297           default:
2298             ddwwStr = buf1;
2299             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2300             break;
2301         }
2302         switch (option) {
2303           case TN_ECHO:
2304             optionStr = "ECHO";
2305             break;
2306           default:
2307             optionStr = buf2;
2308             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2309             break;
2310         }
2311         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2312     }
2313     msg[0] = TN_IAC;
2314     msg[1] = ddww;
2315     msg[2] = option;
2316     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2317     if (outCount < 3) {
2318         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2319     }
2320 }
2321
2322 void
2323 DoEcho ()
2324 {
2325     if (!appData.icsActive) return;
2326     TelnetRequest(TN_DO, TN_ECHO);
2327 }
2328
2329 void
2330 DontEcho ()
2331 {
2332     if (!appData.icsActive) return;
2333     TelnetRequest(TN_DONT, TN_ECHO);
2334 }
2335
2336 void
2337 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2338 {
2339     /* put the holdings sent to us by the server on the board holdings area */
2340     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2341     char p;
2342     ChessSquare piece;
2343
2344     if(gameInfo.holdingsWidth < 2)  return;
2345     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2346         return; // prevent overwriting by pre-board holdings
2347
2348     if( (int)lowestPiece >= BlackPawn ) {
2349         holdingsColumn = 0;
2350         countsColumn = 1;
2351         holdingsStartRow = BOARD_HEIGHT-1;
2352         direction = -1;
2353     } else {
2354         holdingsColumn = BOARD_WIDTH-1;
2355         countsColumn = BOARD_WIDTH-2;
2356         holdingsStartRow = 0;
2357         direction = 1;
2358     }
2359
2360     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2361         board[i][holdingsColumn] = EmptySquare;
2362         board[i][countsColumn]   = (ChessSquare) 0;
2363     }
2364     while( (p=*holdings++) != NULLCHAR ) {
2365         piece = CharToPiece( ToUpper(p) );
2366         if(piece == EmptySquare) continue;
2367         /*j = (int) piece - (int) WhitePawn;*/
2368         j = PieceToNumber(piece);
2369         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2370         if(j < 0) continue;               /* should not happen */
2371         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2372         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2373         board[holdingsStartRow+j*direction][countsColumn]++;
2374     }
2375 }
2376
2377
2378 void
2379 VariantSwitch (Board board, VariantClass newVariant)
2380 {
2381    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2382    static Board oldBoard;
2383
2384    startedFromPositionFile = FALSE;
2385    if(gameInfo.variant == newVariant) return;
2386
2387    /* [HGM] This routine is called each time an assignment is made to
2388     * gameInfo.variant during a game, to make sure the board sizes
2389     * are set to match the new variant. If that means adding or deleting
2390     * holdings, we shift the playing board accordingly
2391     * This kludge is needed because in ICS observe mode, we get boards
2392     * of an ongoing game without knowing the variant, and learn about the
2393     * latter only later. This can be because of the move list we requested,
2394     * in which case the game history is refilled from the beginning anyway,
2395     * but also when receiving holdings of a crazyhouse game. In the latter
2396     * case we want to add those holdings to the already received position.
2397     */
2398
2399
2400    if (appData.debugMode) {
2401      fprintf(debugFP, "Switch board from %s to %s\n",
2402              VariantName(gameInfo.variant), VariantName(newVariant));
2403      setbuf(debugFP, NULL);
2404    }
2405    shuffleOpenings = 0;       /* [HGM] shuffle */
2406    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2407    switch(newVariant)
2408      {
2409      case VariantShogi:
2410        newWidth = 9;  newHeight = 9;
2411        gameInfo.holdingsSize = 7;
2412      case VariantBughouse:
2413      case VariantCrazyhouse:
2414        newHoldingsWidth = 2; break;
2415      case VariantGreat:
2416        newWidth = 10;
2417      case VariantSuper:
2418        newHoldingsWidth = 2;
2419        gameInfo.holdingsSize = 8;
2420        break;
2421      case VariantGothic:
2422      case VariantCapablanca:
2423      case VariantCapaRandom:
2424        newWidth = 10;
2425      default:
2426        newHoldingsWidth = gameInfo.holdingsSize = 0;
2427      };
2428
2429    if(newWidth  != gameInfo.boardWidth  ||
2430       newHeight != gameInfo.boardHeight ||
2431       newHoldingsWidth != gameInfo.holdingsWidth ) {
2432
2433      /* shift position to new playing area, if needed */
2434      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2435        for(i=0; i<BOARD_HEIGHT; i++)
2436          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2437            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2438              board[i][j];
2439        for(i=0; i<newHeight; i++) {
2440          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2441          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2442        }
2443      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2444        for(i=0; i<BOARD_HEIGHT; i++)
2445          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2446            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2447              board[i][j];
2448      }
2449      gameInfo.boardWidth  = newWidth;
2450      gameInfo.boardHeight = newHeight;
2451      gameInfo.holdingsWidth = newHoldingsWidth;
2452      gameInfo.variant = newVariant;
2453      InitDrawingSizes(-2, 0);
2454    } else gameInfo.variant = newVariant;
2455    CopyBoard(oldBoard, board);   // remember correctly formatted board
2456      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2457    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2458 }
2459
2460 static int loggedOn = FALSE;
2461
2462 /*-- Game start info cache: --*/
2463 int gs_gamenum;
2464 char gs_kind[MSG_SIZ];
2465 static char player1Name[128] = "";
2466 static char player2Name[128] = "";
2467 static char cont_seq[] = "\n\\   ";
2468 static int player1Rating = -1;
2469 static int player2Rating = -1;
2470 /*----------------------------*/
2471
2472 ColorClass curColor = ColorNormal;
2473 int suppressKibitz = 0;
2474
2475 // [HGM] seekgraph
2476 Boolean soughtPending = FALSE;
2477 Boolean seekGraphUp;
2478 #define MAX_SEEK_ADS 200
2479 #define SQUARE 0x80
2480 char *seekAdList[MAX_SEEK_ADS];
2481 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2482 float tcList[MAX_SEEK_ADS];
2483 char colorList[MAX_SEEK_ADS];
2484 int nrOfSeekAds = 0;
2485 int minRating = 1010, maxRating = 2800;
2486 int hMargin = 10, vMargin = 20, h, w;
2487 extern int squareSize, lineGap;
2488
2489 void
2490 PlotSeekAd (int i)
2491 {
2492         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2493         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2494         if(r < minRating+100 && r >=0 ) r = minRating+100;
2495         if(r > maxRating) r = maxRating;
2496         if(tc < 1.) tc = 1.;
2497         if(tc > 95.) tc = 95.;
2498         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2499         y = ((double)r - minRating)/(maxRating - minRating)
2500             * (h-vMargin-squareSize/8-1) + vMargin;
2501         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2502         if(strstr(seekAdList[i], " u ")) color = 1;
2503         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2504            !strstr(seekAdList[i], "bullet") &&
2505            !strstr(seekAdList[i], "blitz") &&
2506            !strstr(seekAdList[i], "standard") ) color = 2;
2507         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2508         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2509 }
2510
2511 void
2512 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2513 {
2514         char buf[MSG_SIZ], *ext = "";
2515         VariantClass v = StringToVariant(type);
2516         if(strstr(type, "wild")) {
2517             ext = type + 4; // append wild number
2518             if(v == VariantFischeRandom) type = "chess960"; else
2519             if(v == VariantLoadable) type = "setup"; else
2520             type = VariantName(v);
2521         }
2522         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2523         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2524             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2525             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2526             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2527             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2528             seekNrList[nrOfSeekAds] = nr;
2529             zList[nrOfSeekAds] = 0;
2530             seekAdList[nrOfSeekAds++] = StrSave(buf);
2531             if(plot) PlotSeekAd(nrOfSeekAds-1);
2532         }
2533 }
2534
2535 void
2536 EraseSeekDot (int i)
2537 {
2538     int x = xList[i], y = yList[i], d=squareSize/4, k;
2539     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2540     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2541     // now replot every dot that overlapped
2542     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2543         int xx = xList[k], yy = yList[k];
2544         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2545             DrawSeekDot(xx, yy, colorList[k]);
2546     }
2547 }
2548
2549 void
2550 RemoveSeekAd (int nr)
2551 {
2552         int i;
2553         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2554             EraseSeekDot(i);
2555             if(seekAdList[i]) free(seekAdList[i]);
2556             seekAdList[i] = seekAdList[--nrOfSeekAds];
2557             seekNrList[i] = seekNrList[nrOfSeekAds];
2558             ratingList[i] = ratingList[nrOfSeekAds];
2559             colorList[i]  = colorList[nrOfSeekAds];
2560             tcList[i] = tcList[nrOfSeekAds];
2561             xList[i]  = xList[nrOfSeekAds];
2562             yList[i]  = yList[nrOfSeekAds];
2563             zList[i]  = zList[nrOfSeekAds];
2564             seekAdList[nrOfSeekAds] = NULL;
2565             break;
2566         }
2567 }
2568
2569 Boolean
2570 MatchSoughtLine (char *line)
2571 {
2572     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2573     int nr, base, inc, u=0; char dummy;
2574
2575     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2576        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2577        (u=1) &&
2578        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2579         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2580         // match: compact and save the line
2581         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2582         return TRUE;
2583     }
2584     return FALSE;
2585 }
2586
2587 int
2588 DrawSeekGraph ()
2589 {
2590     int i;
2591     if(!seekGraphUp) return FALSE;
2592     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2593     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2594
2595     DrawSeekBackground(0, 0, w, h);
2596     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2597     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2598     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2599         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2600         yy = h-1-yy;
2601         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2602         if(i%500 == 0) {
2603             char buf[MSG_SIZ];
2604             snprintf(buf, MSG_SIZ, "%d", i);
2605             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2606         }
2607     }
2608     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2609     for(i=1; i<100; i+=(i<10?1:5)) {
2610         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2611         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2612         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2613             char buf[MSG_SIZ];
2614             snprintf(buf, MSG_SIZ, "%d", i);
2615             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2616         }
2617     }
2618     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2619     return TRUE;
2620 }
2621
2622 int
2623 SeekGraphClick (ClickType click, int x, int y, int moving)
2624 {
2625     static int lastDown = 0, displayed = 0, lastSecond;
2626     if(y < 0) return FALSE;
2627     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2628         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2629         if(!seekGraphUp) return FALSE;
2630         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2631         DrawPosition(TRUE, NULL);
2632         return TRUE;
2633     }
2634     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2635         if(click == Release || moving) return FALSE;
2636         nrOfSeekAds = 0;
2637         soughtPending = TRUE;
2638         SendToICS(ics_prefix);
2639         SendToICS("sought\n"); // should this be "sought all"?
2640     } else { // issue challenge based on clicked ad
2641         int dist = 10000; int i, closest = 0, second = 0;
2642         for(i=0; i<nrOfSeekAds; i++) {
2643             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2644             if(d < dist) { dist = d; closest = i; }
2645             second += (d - zList[i] < 120); // count in-range ads
2646             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2647         }
2648         if(dist < 120) {
2649             char buf[MSG_SIZ];
2650             second = (second > 1);
2651             if(displayed != closest || second != lastSecond) {
2652                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2653                 lastSecond = second; displayed = closest;
2654             }
2655             if(click == Press) {
2656                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2657                 lastDown = closest;
2658                 return TRUE;
2659             } // on press 'hit', only show info
2660             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2661             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2662             SendToICS(ics_prefix);
2663             SendToICS(buf);
2664             return TRUE; // let incoming board of started game pop down the graph
2665         } else if(click == Release) { // release 'miss' is ignored
2666             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2667             if(moving == 2) { // right up-click
2668                 nrOfSeekAds = 0; // refresh graph
2669                 soughtPending = TRUE;
2670                 SendToICS(ics_prefix);
2671                 SendToICS("sought\n"); // should this be "sought all"?
2672             }
2673             return TRUE;
2674         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2675         // press miss or release hit 'pop down' seek graph
2676         seekGraphUp = FALSE;
2677         DrawPosition(TRUE, NULL);
2678     }
2679     return TRUE;
2680 }
2681
2682 void
2683 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2684 {
2685 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2686 #define STARTED_NONE 0
2687 #define STARTED_MOVES 1
2688 #define STARTED_BOARD 2
2689 #define STARTED_OBSERVE 3
2690 #define STARTED_HOLDINGS 4
2691 #define STARTED_CHATTER 5
2692 #define STARTED_COMMENT 6
2693 #define STARTED_MOVES_NOHIDE 7
2694
2695     static int started = STARTED_NONE;
2696     static char parse[20000];
2697     static int parse_pos = 0;
2698     static char buf[BUF_SIZE + 1];
2699     static int firstTime = TRUE, intfSet = FALSE;
2700     static ColorClass prevColor = ColorNormal;
2701     static int savingComment = FALSE;
2702     static int cmatch = 0; // continuation sequence match
2703     char *bp;
2704     char str[MSG_SIZ];
2705     int i, oldi;
2706     int buf_len;
2707     int next_out;
2708     int tkind;
2709     int backup;    /* [DM] For zippy color lines */
2710     char *p;
2711     char talker[MSG_SIZ]; // [HGM] chat
2712     int channel;
2713
2714     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2715
2716     if (appData.debugMode) {
2717       if (!error) {
2718         fprintf(debugFP, "<ICS: ");
2719         show_bytes(debugFP, data, count);
2720         fprintf(debugFP, "\n");
2721       }
2722     }
2723
2724     if (appData.debugMode) { int f = forwardMostMove;
2725         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2726                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2727                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2728     }
2729     if (count > 0) {
2730         /* If last read ended with a partial line that we couldn't parse,
2731            prepend it to the new read and try again. */
2732         if (leftover_len > 0) {
2733             for (i=0; i<leftover_len; i++)
2734               buf[i] = buf[leftover_start + i];
2735         }
2736
2737     /* copy new characters into the buffer */
2738     bp = buf + leftover_len;
2739     buf_len=leftover_len;
2740     for (i=0; i<count; i++)
2741     {
2742         // ignore these
2743         if (data[i] == '\r')
2744             continue;
2745
2746         // join lines split by ICS?
2747         if (!appData.noJoin)
2748         {
2749             /*
2750                 Joining just consists of finding matches against the
2751                 continuation sequence, and discarding that sequence
2752                 if found instead of copying it.  So, until a match
2753                 fails, there's nothing to do since it might be the
2754                 complete sequence, and thus, something we don't want
2755                 copied.
2756             */
2757             if (data[i] == cont_seq[cmatch])
2758             {
2759                 cmatch++;
2760                 if (cmatch == strlen(cont_seq))
2761                 {
2762                     cmatch = 0; // complete match.  just reset the counter
2763
2764                     /*
2765                         it's possible for the ICS to not include the space
2766                         at the end of the last word, making our [correct]
2767                         join operation fuse two separate words.  the server
2768                         does this when the space occurs at the width setting.
2769                     */
2770                     if (!buf_len || buf[buf_len-1] != ' ')
2771                     {
2772                         *bp++ = ' ';
2773                         buf_len++;
2774                     }
2775                 }
2776                 continue;
2777             }
2778             else if (cmatch)
2779             {
2780                 /*
2781                     match failed, so we have to copy what matched before
2782                     falling through and copying this character.  In reality,
2783                     this will only ever be just the newline character, but
2784                     it doesn't hurt to be precise.
2785                 */
2786                 strncpy(bp, cont_seq, cmatch);
2787                 bp += cmatch;
2788                 buf_len += cmatch;
2789                 cmatch = 0;
2790             }
2791         }
2792
2793         // copy this char
2794         *bp++ = data[i];
2795         buf_len++;
2796     }
2797
2798         buf[buf_len] = NULLCHAR;
2799 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2800         next_out = 0;
2801         leftover_start = 0;
2802
2803         i = 0;
2804         while (i < buf_len) {
2805             /* Deal with part of the TELNET option negotiation
2806                protocol.  We refuse to do anything beyond the
2807                defaults, except that we allow the WILL ECHO option,
2808                which ICS uses to turn off password echoing when we are
2809                directly connected to it.  We reject this option
2810                if localLineEditing mode is on (always on in xboard)
2811                and we are talking to port 23, which might be a real
2812                telnet server that will try to keep WILL ECHO on permanently.
2813              */
2814             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2815                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2816                 unsigned char option;
2817                 oldi = i;
2818                 switch ((unsigned char) buf[++i]) {
2819                   case TN_WILL:
2820                     if (appData.debugMode)
2821                       fprintf(debugFP, "\n<WILL ");
2822                     switch (option = (unsigned char) buf[++i]) {
2823                       case TN_ECHO:
2824                         if (appData.debugMode)
2825                           fprintf(debugFP, "ECHO ");
2826                         /* Reply only if this is a change, according
2827                            to the protocol rules. */
2828                         if (remoteEchoOption) break;
2829                         if (appData.localLineEditing &&
2830                             atoi(appData.icsPort) == TN_PORT) {
2831                             TelnetRequest(TN_DONT, TN_ECHO);
2832                         } else {
2833                             EchoOff();
2834                             TelnetRequest(TN_DO, TN_ECHO);
2835                             remoteEchoOption = TRUE;
2836                         }
2837                         break;
2838                       default:
2839                         if (appData.debugMode)
2840                           fprintf(debugFP, "%d ", option);
2841                         /* Whatever this is, we don't want it. */
2842                         TelnetRequest(TN_DONT, option);
2843                         break;
2844                     }
2845                     break;
2846                   case TN_WONT:
2847                     if (appData.debugMode)
2848                       fprintf(debugFP, "\n<WONT ");
2849                     switch (option = (unsigned char) buf[++i]) {
2850                       case TN_ECHO:
2851                         if (appData.debugMode)
2852                           fprintf(debugFP, "ECHO ");
2853                         /* Reply only if this is a change, according
2854                            to the protocol rules. */
2855                         if (!remoteEchoOption) break;
2856                         EchoOn();
2857                         TelnetRequest(TN_DONT, TN_ECHO);
2858                         remoteEchoOption = FALSE;
2859                         break;
2860                       default:
2861                         if (appData.debugMode)
2862                           fprintf(debugFP, "%d ", (unsigned char) option);
2863                         /* Whatever this is, it must already be turned
2864                            off, because we never agree to turn on
2865                            anything non-default, so according to the
2866                            protocol rules, we don't reply. */
2867                         break;
2868                     }
2869                     break;
2870                   case TN_DO:
2871                     if (appData.debugMode)
2872                       fprintf(debugFP, "\n<DO ");
2873                     switch (option = (unsigned char) buf[++i]) {
2874                       default:
2875                         /* Whatever this is, we refuse to do it. */
2876                         if (appData.debugMode)
2877                           fprintf(debugFP, "%d ", option);
2878                         TelnetRequest(TN_WONT, option);
2879                         break;
2880                     }
2881                     break;
2882                   case TN_DONT:
2883                     if (appData.debugMode)
2884                       fprintf(debugFP, "\n<DONT ");
2885                     switch (option = (unsigned char) buf[++i]) {
2886                       default:
2887                         if (appData.debugMode)
2888                           fprintf(debugFP, "%d ", option);
2889                         /* Whatever this is, we are already not doing
2890                            it, because we never agree to do anything
2891                            non-default, so according to the protocol
2892                            rules, we don't reply. */
2893                         break;
2894                     }
2895                     break;
2896                   case TN_IAC:
2897                     if (appData.debugMode)
2898                       fprintf(debugFP, "\n<IAC ");
2899                     /* Doubled IAC; pass it through */
2900                     i--;
2901                     break;
2902                   default:
2903                     if (appData.debugMode)
2904                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2905                     /* Drop all other telnet commands on the floor */
2906                     break;
2907                 }
2908                 if (oldi > next_out)
2909                   SendToPlayer(&buf[next_out], oldi - next_out);
2910                 if (++i > next_out)
2911                   next_out = i;
2912                 continue;
2913             }
2914
2915             /* OK, this at least will *usually* work */
2916             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2917                 loggedOn = TRUE;
2918             }
2919
2920             if (loggedOn && !intfSet) {
2921                 if (ics_type == ICS_ICC) {
2922                   snprintf(str, MSG_SIZ,
2923                           "/set-quietly interface %s\n/set-quietly style 12\n",
2924                           programVersion);
2925                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2926                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2927                 } else if (ics_type == ICS_CHESSNET) {
2928                   snprintf(str, MSG_SIZ, "/style 12\n");
2929                 } else {
2930                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2931                   strcat(str, programVersion);
2932                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2933                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2934                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2935 #ifdef WIN32
2936                   strcat(str, "$iset nohighlight 1\n");
2937 #endif
2938                   strcat(str, "$iset lock 1\n$style 12\n");
2939                 }
2940                 SendToICS(str);
2941                 NotifyFrontendLogin();
2942                 intfSet = TRUE;
2943             }
2944
2945             if (started == STARTED_COMMENT) {
2946                 /* Accumulate characters in comment */
2947                 parse[parse_pos++] = buf[i];
2948                 if (buf[i] == '\n') {
2949                     parse[parse_pos] = NULLCHAR;
2950                     if(chattingPartner>=0) {
2951                         char mess[MSG_SIZ];
2952                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2953                         OutputChatMessage(chattingPartner, mess);
2954                         chattingPartner = -1;
2955                         next_out = i+1; // [HGM] suppress printing in ICS window
2956                     } else
2957                     if(!suppressKibitz) // [HGM] kibitz
2958                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2959                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2960                         int nrDigit = 0, nrAlph = 0, j;
2961                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2962                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2963                         parse[parse_pos] = NULLCHAR;
2964                         // try to be smart: if it does not look like search info, it should go to
2965                         // ICS interaction window after all, not to engine-output window.
2966                         for(j=0; j<parse_pos; j++) { // count letters and digits
2967                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2968                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2969                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2970                         }
2971                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2972                             int depth=0; float score;
2973                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2974                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2975                                 pvInfoList[forwardMostMove-1].depth = depth;
2976                                 pvInfoList[forwardMostMove-1].score = 100*score;
2977                             }
2978                             OutputKibitz(suppressKibitz, parse);
2979                         } else {
2980                             char tmp[MSG_SIZ];
2981                             if(gameMode == IcsObserving) // restore original ICS messages
2982                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2983                             else
2984                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2985                             SendToPlayer(tmp, strlen(tmp));
2986                         }
2987                         next_out = i+1; // [HGM] suppress printing in ICS window
2988                     }
2989                     started = STARTED_NONE;
2990                 } else {
2991                     /* Don't match patterns against characters in comment */
2992                     i++;
2993                     continue;
2994                 }
2995             }
2996             if (started == STARTED_CHATTER) {
2997                 if (buf[i] != '\n') {
2998                     /* Don't match patterns against characters in chatter */
2999                     i++;
3000                     continue;
3001                 }
3002                 started = STARTED_NONE;
3003                 if(suppressKibitz) next_out = i+1;
3004             }
3005
3006             /* Kludge to deal with rcmd protocol */
3007             if (firstTime && looking_at(buf, &i, "\001*")) {
3008                 DisplayFatalError(&buf[1], 0, 1);
3009                 continue;
3010             } else {
3011                 firstTime = FALSE;
3012             }
3013
3014             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3015                 ics_type = ICS_ICC;
3016                 ics_prefix = "/";
3017                 if (appData.debugMode)
3018                   fprintf(debugFP, "ics_type %d\n", ics_type);
3019                 continue;
3020             }
3021             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3022                 ics_type = ICS_FICS;
3023                 ics_prefix = "$";
3024                 if (appData.debugMode)
3025                   fprintf(debugFP, "ics_type %d\n", ics_type);
3026                 continue;
3027             }
3028             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3029                 ics_type = ICS_CHESSNET;
3030                 ics_prefix = "/";
3031                 if (appData.debugMode)
3032                   fprintf(debugFP, "ics_type %d\n", ics_type);
3033                 continue;
3034             }
3035
3036             if (!loggedOn &&
3037                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3038                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3039                  looking_at(buf, &i, "will be \"*\""))) {
3040               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3041               continue;
3042             }
3043
3044             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3045               char buf[MSG_SIZ];
3046               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3047               DisplayIcsInteractionTitle(buf);
3048               have_set_title = TRUE;
3049             }
3050
3051             /* skip finger notes */
3052             if (started == STARTED_NONE &&
3053                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3054                  (buf[i] == '1' && buf[i+1] == '0')) &&
3055                 buf[i+2] == ':' && buf[i+3] == ' ') {
3056               started = STARTED_CHATTER;
3057               i += 3;
3058               continue;
3059             }
3060
3061             oldi = i;
3062             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3063             if(appData.seekGraph) {
3064                 if(soughtPending && MatchSoughtLine(buf+i)) {
3065                     i = strstr(buf+i, "rated") - buf;
3066                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3067                     next_out = leftover_start = i;
3068                     started = STARTED_CHATTER;
3069                     suppressKibitz = TRUE;
3070                     continue;
3071                 }
3072                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3073                         && looking_at(buf, &i, "* ads displayed")) {
3074                     soughtPending = FALSE;
3075                     seekGraphUp = TRUE;
3076                     DrawSeekGraph();
3077                     continue;
3078                 }
3079                 if(appData.autoRefresh) {
3080                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3081                         int s = (ics_type == ICS_ICC); // ICC format differs
3082                         if(seekGraphUp)
3083                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3084                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3085                         looking_at(buf, &i, "*% "); // eat prompt
3086                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3087                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3088                         next_out = i; // suppress
3089                         continue;
3090                     }
3091                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3092                         char *p = star_match[0];
3093                         while(*p) {
3094                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3095                             while(*p && *p++ != ' '); // next
3096                         }
3097                         looking_at(buf, &i, "*% "); // eat prompt
3098                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3099                         next_out = i;
3100                         continue;
3101                     }
3102                 }
3103             }
3104
3105             /* skip formula vars */
3106             if (started == STARTED_NONE &&
3107                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3108               started = STARTED_CHATTER;
3109               i += 3;
3110               continue;
3111             }
3112
3113             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3114             if (appData.autoKibitz && started == STARTED_NONE &&
3115                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3116                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3117                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3118                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3119                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3120                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3121                         suppressKibitz = TRUE;
3122                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3123                         next_out = i;
3124                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3125                                 && (gameMode == IcsPlayingWhite)) ||
3126                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3127                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3128                             started = STARTED_CHATTER; // own kibitz we simply discard
3129                         else {
3130                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3131                             parse_pos = 0; parse[0] = NULLCHAR;
3132                             savingComment = TRUE;
3133                             suppressKibitz = gameMode != IcsObserving ? 2 :
3134                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3135                         }
3136                         continue;
3137                 } else
3138                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3139                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3140                          && atoi(star_match[0])) {
3141                     // suppress the acknowledgements of our own autoKibitz
3142                     char *p;
3143                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3144                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3145                     SendToPlayer(star_match[0], strlen(star_match[0]));
3146                     if(looking_at(buf, &i, "*% ")) // eat prompt
3147                         suppressKibitz = FALSE;
3148                     next_out = i;
3149                     continue;
3150                 }
3151             } // [HGM] kibitz: end of patch
3152
3153             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3154
3155             // [HGM] chat: intercept tells by users for which we have an open chat window
3156             channel = -1;
3157             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3158                                            looking_at(buf, &i, "* whispers:") ||
3159                                            looking_at(buf, &i, "* kibitzes:") ||
3160                                            looking_at(buf, &i, "* shouts:") ||
3161                                            looking_at(buf, &i, "* c-shouts:") ||
3162                                            looking_at(buf, &i, "--> * ") ||
3163                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3164                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3165                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3166                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3167                 int p;
3168                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3169                 chattingPartner = -1;
3170
3171                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3172                 for(p=0; p<MAX_CHAT; p++) {
3173                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3174                     talker[0] = '['; strcat(talker, "] ");
3175                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3176                     chattingPartner = p; break;
3177                     }
3178                 } else
3179                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3180                 for(p=0; p<MAX_CHAT; p++) {
3181                     if(!strcmp("kibitzes", chatPartner[p])) {
3182                         talker[0] = '['; strcat(talker, "] ");
3183                         chattingPartner = p; break;
3184                     }
3185                 } else
3186                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3187                 for(p=0; p<MAX_CHAT; p++) {
3188                     if(!strcmp("whispers", chatPartner[p])) {
3189                         talker[0] = '['; strcat(talker, "] ");
3190                         chattingPartner = p; break;
3191                     }
3192                 } else
3193                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3194                   if(buf[i-8] == '-' && buf[i-3] == 't')
3195                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3196                     if(!strcmp("c-shouts", chatPartner[p])) {
3197                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3198                         chattingPartner = p; break;
3199                     }
3200                   }
3201                   if(chattingPartner < 0)
3202                   for(p=0; p<MAX_CHAT; p++) {
3203                     if(!strcmp("shouts", chatPartner[p])) {
3204                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3205                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3206                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3207                         chattingPartner = p; break;
3208                     }
3209                   }
3210                 }
3211                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3212                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3213                     talker[0] = 0; Colorize(ColorTell, FALSE);
3214                     chattingPartner = p; break;
3215                 }
3216                 if(chattingPartner<0) i = oldi; else {
3217                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3218                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3219                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3220                     started = STARTED_COMMENT;
3221                     parse_pos = 0; parse[0] = NULLCHAR;
3222                     savingComment = 3 + chattingPartner; // counts as TRUE
3223                     suppressKibitz = TRUE;
3224                     continue;
3225                 }
3226             } // [HGM] chat: end of patch
3227
3228           backup = i;
3229             if (appData.zippyTalk || appData.zippyPlay) {
3230                 /* [DM] Backup address for color zippy lines */
3231 #if ZIPPY
3232                if (loggedOn == TRUE)
3233                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3234                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3235 #endif
3236             } // [DM] 'else { ' deleted
3237                 if (
3238                     /* Regular tells and says */
3239                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3240                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3241                     looking_at(buf, &i, "* says: ") ||
3242                     /* Don't color "message" or "messages" output */
3243                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3244                     looking_at(buf, &i, "*. * at *:*: ") ||
3245                     looking_at(buf, &i, "--* (*:*): ") ||
3246                     /* Message notifications (same color as tells) */
3247                     looking_at(buf, &i, "* has left a message ") ||
3248                     looking_at(buf, &i, "* just sent you a message:\n") ||
3249                     /* Whispers and kibitzes */
3250                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3251                     looking_at(buf, &i, "* kibitzes: ") ||
3252                     /* Channel tells */
3253                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3254
3255                   if (tkind == 1 && strchr(star_match[0], ':')) {
3256                       /* Avoid "tells you:" spoofs in channels */
3257                      tkind = 3;
3258                   }
3259                   if (star_match[0][0] == NULLCHAR ||
3260                       strchr(star_match[0], ' ') ||
3261                       (tkind == 3 && strchr(star_match[1], ' '))) {
3262                     /* Reject bogus matches */
3263                     i = oldi;
3264                   } else {
3265                     if (appData.colorize) {
3266                       if (oldi > next_out) {
3267                         SendToPlayer(&buf[next_out], oldi - next_out);
3268                         next_out = oldi;
3269                       }
3270                       switch (tkind) {
3271                       case 1:
3272                         Colorize(ColorTell, FALSE);
3273                         curColor = ColorTell;
3274                         break;
3275                       case 2:
3276                         Colorize(ColorKibitz, FALSE);
3277                         curColor = ColorKibitz;
3278                         break;
3279                       case 3:
3280                         p = strrchr(star_match[1], '(');
3281                         if (p == NULL) {
3282                           p = star_match[1];
3283                         } else {
3284                           p++;
3285                         }
3286                         if (atoi(p) == 1) {
3287                           Colorize(ColorChannel1, FALSE);
3288                           curColor = ColorChannel1;
3289                         } else {
3290                           Colorize(ColorChannel, FALSE);
3291                           curColor = ColorChannel;
3292                         }
3293                         break;
3294                       case 5:
3295                         curColor = ColorNormal;
3296                         break;
3297                       }
3298                     }
3299                     if (started == STARTED_NONE && appData.autoComment &&
3300                         (gameMode == IcsObserving ||
3301                          gameMode == IcsPlayingWhite ||
3302                          gameMode == IcsPlayingBlack)) {
3303                       parse_pos = i - oldi;
3304                       memcpy(parse, &buf[oldi], parse_pos);
3305                       parse[parse_pos] = NULLCHAR;
3306                       started = STARTED_COMMENT;
3307                       savingComment = TRUE;
3308                     } else {
3309                       started = STARTED_CHATTER;
3310                       savingComment = FALSE;
3311                     }
3312                     loggedOn = TRUE;
3313                     continue;
3314                   }
3315                 }
3316
3317                 if (looking_at(buf, &i, "* s-shouts: ") ||
3318                     looking_at(buf, &i, "* c-shouts: ")) {
3319                     if (appData.colorize) {
3320                         if (oldi > next_out) {
3321                             SendToPlayer(&buf[next_out], oldi - next_out);
3322                             next_out = oldi;
3323                         }
3324                         Colorize(ColorSShout, FALSE);
3325                         curColor = ColorSShout;
3326                     }
3327                     loggedOn = TRUE;
3328                     started = STARTED_CHATTER;
3329                     continue;
3330                 }
3331
3332                 if (looking_at(buf, &i, "--->")) {
3333                     loggedOn = TRUE;
3334                     continue;
3335                 }
3336
3337                 if (looking_at(buf, &i, "* shouts: ") ||
3338                     looking_at(buf, &i, "--> ")) {
3339                     if (appData.colorize) {
3340                         if (oldi > next_out) {
3341                             SendToPlayer(&buf[next_out], oldi - next_out);
3342                             next_out = oldi;
3343                         }
3344                         Colorize(ColorShout, FALSE);
3345                         curColor = ColorShout;
3346                     }
3347                     loggedOn = TRUE;
3348                     started = STARTED_CHATTER;
3349                     continue;
3350                 }
3351
3352                 if (looking_at( buf, &i, "Challenge:")) {
3353                     if (appData.colorize) {
3354                         if (oldi > next_out) {
3355                             SendToPlayer(&buf[next_out], oldi - next_out);
3356                             next_out = oldi;
3357                         }
3358                         Colorize(ColorChallenge, FALSE);
3359                         curColor = ColorChallenge;
3360                     }
3361                     loggedOn = TRUE;
3362                     continue;
3363                 }
3364
3365                 if (looking_at(buf, &i, "* offers you") ||
3366                     looking_at(buf, &i, "* offers to be") ||
3367                     looking_at(buf, &i, "* would like to") ||
3368                     looking_at(buf, &i, "* requests to") ||
3369                     looking_at(buf, &i, "Your opponent offers") ||
3370                     looking_at(buf, &i, "Your opponent requests")) {
3371
3372                     if (appData.colorize) {
3373                         if (oldi > next_out) {
3374                             SendToPlayer(&buf[next_out], oldi - next_out);
3375                             next_out = oldi;
3376                         }
3377                         Colorize(ColorRequest, FALSE);
3378                         curColor = ColorRequest;
3379                     }
3380                     continue;
3381                 }
3382
3383                 if (looking_at(buf, &i, "* (*) seeking")) {
3384                     if (appData.colorize) {
3385                         if (oldi > next_out) {
3386                             SendToPlayer(&buf[next_out], oldi - next_out);
3387                             next_out = oldi;
3388                         }
3389                         Colorize(ColorSeek, FALSE);
3390                         curColor = ColorSeek;
3391                     }
3392                     continue;
3393             }
3394
3395           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3396
3397             if (looking_at(buf, &i, "\\   ")) {
3398                 if (prevColor != ColorNormal) {
3399                     if (oldi > next_out) {
3400                         SendToPlayer(&buf[next_out], oldi - next_out);
3401                         next_out = oldi;
3402                     }
3403                     Colorize(prevColor, TRUE);
3404                     curColor = prevColor;
3405                 }
3406                 if (savingComment) {
3407                     parse_pos = i - oldi;
3408                     memcpy(parse, &buf[oldi], parse_pos);
3409                     parse[parse_pos] = NULLCHAR;
3410                     started = STARTED_COMMENT;
3411                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3412                         chattingPartner = savingComment - 3; // kludge to remember the box
3413                 } else {
3414                     started = STARTED_CHATTER;
3415                 }
3416                 continue;
3417             }
3418
3419             if (looking_at(buf, &i, "Black Strength :") ||
3420                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3421                 looking_at(buf, &i, "<10>") ||
3422                 looking_at(buf, &i, "#@#")) {
3423                 /* Wrong board style */
3424                 loggedOn = TRUE;
3425                 SendToICS(ics_prefix);
3426                 SendToICS("set style 12\n");
3427                 SendToICS(ics_prefix);
3428                 SendToICS("refresh\n");
3429                 continue;
3430             }
3431
3432             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3433                 ICSInitScript();
3434                 have_sent_ICS_logon = 1;
3435                 continue;
3436             }
3437
3438             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3439                 (looking_at(buf, &i, "\n<12> ") ||
3440                  looking_at(buf, &i, "<12> "))) {
3441                 loggedOn = TRUE;
3442                 if (oldi > next_out) {
3443                     SendToPlayer(&buf[next_out], oldi - next_out);
3444                 }
3445                 next_out = i;
3446                 started = STARTED_BOARD;
3447                 parse_pos = 0;
3448                 continue;
3449             }
3450
3451             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3452                 looking_at(buf, &i, "<b1> ")) {
3453                 if (oldi > next_out) {
3454                     SendToPlayer(&buf[next_out], oldi - next_out);
3455                 }
3456                 next_out = i;
3457                 started = STARTED_HOLDINGS;
3458                 parse_pos = 0;
3459                 continue;
3460             }
3461
3462             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3463                 loggedOn = TRUE;
3464                 /* Header for a move list -- first line */
3465
3466                 switch (ics_getting_history) {
3467                   case H_FALSE:
3468                     switch (gameMode) {
3469                       case IcsIdle:
3470                       case BeginningOfGame:
3471                         /* User typed "moves" or "oldmoves" while we
3472                            were idle.  Pretend we asked for these
3473                            moves and soak them up so user can step
3474                            through them and/or save them.
3475                            */
3476                         Reset(FALSE, TRUE);
3477                         gameMode = IcsObserving;
3478                         ModeHighlight();
3479                         ics_gamenum = -1;
3480                         ics_getting_history = H_GOT_UNREQ_HEADER;
3481                         break;
3482                       case EditGame: /*?*/
3483                       case EditPosition: /*?*/
3484                         /* Should above feature work in these modes too? */
3485                         /* For now it doesn't */
3486                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3487                         break;
3488                       default:
3489                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3490                         break;
3491                     }
3492                     break;
3493                   case H_REQUESTED:
3494                     /* Is this the right one? */
3495                     if (gameInfo.white && gameInfo.black &&
3496                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3497                         strcmp(gameInfo.black, star_match[2]) == 0) {
3498                         /* All is well */
3499                         ics_getting_history = H_GOT_REQ_HEADER;
3500                     }
3501                     break;
3502                   case H_GOT_REQ_HEADER:
3503                   case H_GOT_UNREQ_HEADER:
3504                   case H_GOT_UNWANTED_HEADER:
3505                   case H_GETTING_MOVES:
3506                     /* Should not happen */
3507                     DisplayError(_("Error gathering move list: two headers"), 0);
3508                     ics_getting_history = H_FALSE;
3509                     break;
3510                 }
3511
3512                 /* Save player ratings into gameInfo if needed */
3513                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3514                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3515                     (gameInfo.whiteRating == -1 ||
3516                      gameInfo.blackRating == -1)) {
3517
3518                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3519                     gameInfo.blackRating = string_to_rating(star_match[3]);
3520                     if (appData.debugMode)
3521                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3522                               gameInfo.whiteRating, gameInfo.blackRating);
3523                 }
3524                 continue;
3525             }
3526
3527             if (looking_at(buf, &i,
3528               "* * match, initial time: * minute*, increment: * second")) {
3529                 /* Header for a move list -- second line */
3530                 /* Initial board will follow if this is a wild game */
3531                 if (gameInfo.event != NULL) free(gameInfo.event);
3532                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3533                 gameInfo.event = StrSave(str);
3534                 /* [HGM] we switched variant. Translate boards if needed. */
3535                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3536                 continue;
3537             }
3538
3539             if (looking_at(buf, &i, "Move  ")) {
3540                 /* Beginning of a move list */
3541                 switch (ics_getting_history) {
3542                   case H_FALSE:
3543                     /* Normally should not happen */
3544                     /* Maybe user hit reset while we were parsing */
3545                     break;
3546                   case H_REQUESTED:
3547                     /* Happens if we are ignoring a move list that is not
3548                      * the one we just requested.  Common if the user
3549                      * tries to observe two games without turning off
3550                      * getMoveList */
3551                     break;
3552                   case H_GETTING_MOVES:
3553                     /* Should not happen */
3554                     DisplayError(_("Error gathering move list: nested"), 0);
3555                     ics_getting_history = H_FALSE;
3556                     break;
3557                   case H_GOT_REQ_HEADER:
3558                     ics_getting_history = H_GETTING_MOVES;
3559                     started = STARTED_MOVES;
3560                     parse_pos = 0;
3561                     if (oldi > next_out) {
3562                         SendToPlayer(&buf[next_out], oldi - next_out);
3563                     }
3564                     break;
3565                   case H_GOT_UNREQ_HEADER:
3566                     ics_getting_history = H_GETTING_MOVES;
3567                     started = STARTED_MOVES_NOHIDE;
3568                     parse_pos = 0;
3569                     break;
3570                   case H_GOT_UNWANTED_HEADER:
3571                     ics_getting_history = H_FALSE;
3572                     break;
3573                 }
3574                 continue;
3575             }
3576
3577             if (looking_at(buf, &i, "% ") ||
3578                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3579                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3580                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3581                     soughtPending = FALSE;
3582                     seekGraphUp = TRUE;
3583                     DrawSeekGraph();
3584                 }
3585                 if(suppressKibitz) next_out = i;
3586                 savingComment = FALSE;
3587                 suppressKibitz = 0;
3588                 switch (started) {
3589                   case STARTED_MOVES:
3590                   case STARTED_MOVES_NOHIDE:
3591                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3592                     parse[parse_pos + i - oldi] = NULLCHAR;
3593                     ParseGameHistory(parse);
3594 #if ZIPPY
3595                     if (appData.zippyPlay && first.initDone) {
3596                         FeedMovesToProgram(&first, forwardMostMove);
3597                         if (gameMode == IcsPlayingWhite) {
3598                             if (WhiteOnMove(forwardMostMove)) {
3599                                 if (first.sendTime) {
3600                                   if (first.useColors) {
3601                                     SendToProgram("black\n", &first);
3602                                   }
3603                                   SendTimeRemaining(&first, TRUE);
3604                                 }
3605                                 if (first.useColors) {
3606                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3607                                 }
3608                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3609                                 first.maybeThinking = TRUE;
3610                             } else {
3611                                 if (first.usePlayother) {
3612                                   if (first.sendTime) {
3613                                     SendTimeRemaining(&first, TRUE);
3614                                   }
3615                                   SendToProgram("playother\n", &first);
3616                                   firstMove = FALSE;
3617                                 } else {
3618                                   firstMove = TRUE;
3619                                 }
3620                             }
3621                         } else if (gameMode == IcsPlayingBlack) {
3622                             if (!WhiteOnMove(forwardMostMove)) {
3623                                 if (first.sendTime) {
3624                                   if (first.useColors) {
3625                                     SendToProgram("white\n", &first);
3626                                   }
3627                                   SendTimeRemaining(&first, FALSE);
3628                                 }
3629                                 if (first.useColors) {
3630                                   SendToProgram("black\n", &first);
3631                                 }
3632                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3633                                 first.maybeThinking = TRUE;
3634                             } else {
3635                                 if (first.usePlayother) {
3636                                   if (first.sendTime) {
3637                                     SendTimeRemaining(&first, FALSE);
3638                                   }
3639                                   SendToProgram("playother\n", &first);
3640                                   firstMove = FALSE;
3641                                 } else {
3642                                   firstMove = TRUE;
3643                                 }
3644                             }
3645                         }
3646                     }
3647 #endif
3648                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3649                         /* Moves came from oldmoves or moves command
3650                            while we weren't doing anything else.
3651                            */
3652                         currentMove = forwardMostMove;
3653                         ClearHighlights();/*!!could figure this out*/
3654                         flipView = appData.flipView;
3655                         DrawPosition(TRUE, boards[currentMove]);
3656                         DisplayBothClocks();
3657                         snprintf(str, MSG_SIZ, "%s %s %s",
3658                                 gameInfo.white, _("vs."),  gameInfo.black);
3659                         DisplayTitle(str);
3660                         gameMode = IcsIdle;
3661                     } else {
3662                         /* Moves were history of an active game */
3663                         if (gameInfo.resultDetails != NULL) {
3664                             free(gameInfo.resultDetails);
3665                             gameInfo.resultDetails = NULL;
3666                         }
3667                     }
3668                     HistorySet(parseList, backwardMostMove,
3669                                forwardMostMove, currentMove-1);
3670                     DisplayMove(currentMove - 1);
3671                     if (started == STARTED_MOVES) next_out = i;
3672                     started = STARTED_NONE;
3673                     ics_getting_history = H_FALSE;
3674                     break;
3675
3676                   case STARTED_OBSERVE:
3677                     started = STARTED_NONE;
3678                     SendToICS(ics_prefix);
3679                     SendToICS("refresh\n");
3680                     break;
3681
3682                   default:
3683                     break;
3684                 }
3685                 if(bookHit) { // [HGM] book: simulate book reply
3686                     static char bookMove[MSG_SIZ]; // a bit generous?
3687
3688                     programStats.nodes = programStats.depth = programStats.time =
3689                     programStats.score = programStats.got_only_move = 0;
3690                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3691
3692                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3693                     strcat(bookMove, bookHit);
3694                     HandleMachineMove(bookMove, &first);
3695                 }
3696                 continue;
3697             }
3698
3699             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3700                  started == STARTED_HOLDINGS ||
3701                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3702                 /* Accumulate characters in move list or board */
3703                 parse[parse_pos++] = buf[i];
3704             }
3705
3706             /* Start of game messages.  Mostly we detect start of game
3707                when the first board image arrives.  On some versions
3708                of the ICS, though, we need to do a "refresh" after starting
3709                to observe in order to get the current board right away. */
3710             if (looking_at(buf, &i, "Adding game * to observation list")) {
3711                 started = STARTED_OBSERVE;
3712                 continue;
3713             }
3714
3715             /* Handle auto-observe */
3716             if (appData.autoObserve &&
3717                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3718                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3719                 char *player;
3720                 /* Choose the player that was highlighted, if any. */
3721                 if (star_match[0][0] == '\033' ||
3722                     star_match[1][0] != '\033') {
3723                     player = star_match[0];
3724                 } else {
3725                     player = star_match[2];
3726                 }
3727                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3728                         ics_prefix, StripHighlightAndTitle(player));
3729                 SendToICS(str);
3730
3731                 /* Save ratings from notify string */
3732                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3733                 player1Rating = string_to_rating(star_match[1]);
3734                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3735                 player2Rating = string_to_rating(star_match[3]);
3736
3737                 if (appData.debugMode)
3738                   fprintf(debugFP,
3739                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3740                           player1Name, player1Rating,
3741                           player2Name, player2Rating);
3742
3743                 continue;
3744             }
3745
3746             /* Deal with automatic examine mode after a game,
3747                and with IcsObserving -> IcsExamining transition */
3748             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3749                 looking_at(buf, &i, "has made you an examiner of game *")) {
3750
3751                 int gamenum = atoi(star_match[0]);
3752                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3753                     gamenum == ics_gamenum) {
3754                     /* We were already playing or observing this game;
3755                        no need to refetch history */
3756                     gameMode = IcsExamining;
3757                     if (pausing) {
3758                         pauseExamForwardMostMove = forwardMostMove;
3759                     } else if (currentMove < forwardMostMove) {
3760                         ForwardInner(forwardMostMove);
3761                     }
3762                 } else {
3763                     /* I don't think this case really can happen */
3764                     SendToICS(ics_prefix);
3765                     SendToICS("refresh\n");
3766                 }
3767                 continue;
3768             }
3769
3770             /* Error messages */
3771 //          if (ics_user_moved) {
3772             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3773                 if (looking_at(buf, &i, "Illegal move") ||
3774                     looking_at(buf, &i, "Not a legal move") ||
3775                     looking_at(buf, &i, "Your king is in check") ||
3776                     looking_at(buf, &i, "It isn't your turn") ||
3777                     looking_at(buf, &i, "It is not your move")) {
3778                     /* Illegal move */
3779                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3780                         currentMove = forwardMostMove-1;
3781                         DisplayMove(currentMove - 1); /* before DMError */
3782                         DrawPosition(FALSE, boards[currentMove]);
3783                         SwitchClocks(forwardMostMove-1); // [HGM] race
3784                         DisplayBothClocks();
3785                     }
3786                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3787                     ics_user_moved = 0;
3788                     continue;
3789                 }
3790             }
3791
3792             if (looking_at(buf, &i, "still have time") ||
3793                 looking_at(buf, &i, "not out of time") ||
3794                 looking_at(buf, &i, "either player is out of time") ||
3795                 looking_at(buf, &i, "has timeseal; checking")) {
3796                 /* We must have called his flag a little too soon */
3797                 whiteFlag = blackFlag = FALSE;
3798                 continue;
3799             }
3800
3801             if (looking_at(buf, &i, "added * seconds to") ||
3802                 looking_at(buf, &i, "seconds were added to")) {
3803                 /* Update the clocks */
3804                 SendToICS(ics_prefix);
3805                 SendToICS("refresh\n");
3806                 continue;
3807             }
3808
3809             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3810                 ics_clock_paused = TRUE;
3811                 StopClocks();
3812                 continue;
3813             }
3814
3815             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3816                 ics_clock_paused = FALSE;
3817                 StartClocks();
3818                 continue;
3819             }
3820
3821             /* Grab player ratings from the Creating: message.
3822                Note we have to check for the special case when
3823                the ICS inserts things like [white] or [black]. */
3824             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3825                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3826                 /* star_matches:
3827                    0    player 1 name (not necessarily white)
3828                    1    player 1 rating
3829                    2    empty, white, or black (IGNORED)
3830                    3    player 2 name (not necessarily black)
3831                    4    player 2 rating
3832
3833                    The names/ratings are sorted out when the game
3834                    actually starts (below).
3835                 */
3836                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3837                 player1Rating = string_to_rating(star_match[1]);
3838                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3839                 player2Rating = string_to_rating(star_match[4]);
3840
3841                 if (appData.debugMode)
3842                   fprintf(debugFP,
3843                           "Ratings from 'Creating:' %s %d, %s %d\n",
3844                           player1Name, player1Rating,
3845                           player2Name, player2Rating);
3846
3847                 continue;
3848             }
3849
3850             /* Improved generic start/end-of-game messages */
3851             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3852                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3853                 /* If tkind == 0: */
3854                 /* star_match[0] is the game number */
3855                 /*           [1] is the white player's name */
3856                 /*           [2] is the black player's name */
3857                 /* For end-of-game: */
3858                 /*           [3] is the reason for the game end */
3859                 /*           [4] is a PGN end game-token, preceded by " " */
3860                 /* For start-of-game: */
3861                 /*           [3] begins with "Creating" or "Continuing" */
3862                 /*           [4] is " *" or empty (don't care). */
3863                 int gamenum = atoi(star_match[0]);
3864                 char *whitename, *blackname, *why, *endtoken;
3865                 ChessMove endtype = EndOfFile;
3866
3867                 if (tkind == 0) {
3868                   whitename = star_match[1];
3869                   blackname = star_match[2];
3870                   why = star_match[3];
3871                   endtoken = star_match[4];
3872                 } else {
3873                   whitename = star_match[1];
3874                   blackname = star_match[3];
3875                   why = star_match[5];
3876                   endtoken = star_match[6];
3877                 }
3878
3879                 /* Game start messages */
3880                 if (strncmp(why, "Creating ", 9) == 0 ||
3881                     strncmp(why, "Continuing ", 11) == 0) {
3882                     gs_gamenum = gamenum;
3883                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3884                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3885                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3886 #if ZIPPY
3887                     if (appData.zippyPlay) {
3888                         ZippyGameStart(whitename, blackname);
3889                     }
3890 #endif /*ZIPPY*/
3891                     partnerBoardValid = FALSE; // [HGM] bughouse
3892                     continue;
3893                 }
3894
3895                 /* Game end messages */
3896                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3897                     ics_gamenum != gamenum) {
3898                     continue;
3899                 }
3900                 while (endtoken[0] == ' ') endtoken++;
3901                 switch (endtoken[0]) {
3902                   case '*':
3903                   default:
3904                     endtype = GameUnfinished;
3905                     break;
3906                   case '0':
3907                     endtype = BlackWins;
3908                     break;
3909                   case '1':
3910                     if (endtoken[1] == '/')
3911                       endtype = GameIsDrawn;
3912                     else
3913                       endtype = WhiteWins;
3914                     break;
3915                 }
3916                 GameEnds(endtype, why, GE_ICS);
3917 #if ZIPPY
3918                 if (appData.zippyPlay && first.initDone) {
3919                     ZippyGameEnd(endtype, why);
3920                     if (first.pr == NoProc) {
3921                       /* Start the next process early so that we'll
3922                          be ready for the next challenge */
3923                       StartChessProgram(&first);
3924                     }
3925                     /* Send "new" early, in case this command takes
3926                        a long time to finish, so that we'll be ready
3927                        for the next challenge. */
3928                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3929                     Reset(TRUE, TRUE);
3930                 }
3931 #endif /*ZIPPY*/
3932                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3933                 continue;
3934             }
3935
3936             if (looking_at(buf, &i, "Removing game * from observation") ||
3937                 looking_at(buf, &i, "no longer observing game *") ||
3938                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3939                 if (gameMode == IcsObserving &&
3940                     atoi(star_match[0]) == ics_gamenum)
3941                   {
3942                       /* icsEngineAnalyze */
3943                       if (appData.icsEngineAnalyze) {
3944                             ExitAnalyzeMode();
3945                             ModeHighlight();
3946                       }
3947                       StopClocks();
3948                       gameMode = IcsIdle;
3949                       ics_gamenum = -1;
3950                       ics_user_moved = FALSE;
3951                   }
3952                 continue;
3953             }
3954
3955             if (looking_at(buf, &i, "no longer examining game *")) {
3956                 if (gameMode == IcsExamining &&
3957                     atoi(star_match[0]) == ics_gamenum)
3958                   {
3959                       gameMode = IcsIdle;
3960                       ics_gamenum = -1;
3961                       ics_user_moved = FALSE;
3962                   }
3963                 continue;
3964             }
3965
3966             /* Advance leftover_start past any newlines we find,
3967                so only partial lines can get reparsed */
3968             if (looking_at(buf, &i, "\n")) {
3969                 prevColor = curColor;
3970                 if (curColor != ColorNormal) {
3971                     if (oldi > next_out) {
3972                         SendToPlayer(&buf[next_out], oldi - next_out);
3973                         next_out = oldi;
3974                     }
3975                     Colorize(ColorNormal, FALSE);
3976                     curColor = ColorNormal;
3977                 }
3978                 if (started == STARTED_BOARD) {
3979                     started = STARTED_NONE;
3980                     parse[parse_pos] = NULLCHAR;
3981                     ParseBoard12(parse);
3982                     ics_user_moved = 0;
3983
3984                     /* Send premove here */
3985                     if (appData.premove) {
3986                       char str[MSG_SIZ];
3987                       if (currentMove == 0 &&
3988                           gameMode == IcsPlayingWhite &&
3989                           appData.premoveWhite) {
3990                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3991                         if (appData.debugMode)
3992                           fprintf(debugFP, "Sending premove:\n");
3993                         SendToICS(str);
3994                       } else if (currentMove == 1 &&
3995                                  gameMode == IcsPlayingBlack &&
3996                                  appData.premoveBlack) {
3997                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3998                         if (appData.debugMode)
3999                           fprintf(debugFP, "Sending premove:\n");
4000                         SendToICS(str);
4001                       } else if (gotPremove) {
4002                         gotPremove = 0;
4003                         ClearPremoveHighlights();
4004                         if (appData.debugMode)
4005                           fprintf(debugFP, "Sending premove:\n");
4006                           UserMoveEvent(premoveFromX, premoveFromY,
4007                                         premoveToX, premoveToY,
4008                                         premovePromoChar);
4009                       }
4010                     }
4011
4012                     /* Usually suppress following prompt */
4013                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4014                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4015                         if (looking_at(buf, &i, "*% ")) {
4016                             savingComment = FALSE;
4017                             suppressKibitz = 0;
4018                         }
4019                     }
4020                     next_out = i;
4021                 } else if (started == STARTED_HOLDINGS) {
4022                     int gamenum;
4023                     char new_piece[MSG_SIZ];
4024                     started = STARTED_NONE;
4025                     parse[parse_pos] = NULLCHAR;
4026                     if (appData.debugMode)
4027                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4028                                                         parse, currentMove);
4029                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4030                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4031                         if (gameInfo.variant == VariantNormal) {
4032                           /* [HGM] We seem to switch variant during a game!
4033                            * Presumably no holdings were displayed, so we have
4034                            * to move the position two files to the right to
4035                            * create room for them!
4036                            */
4037                           VariantClass newVariant;
4038                           switch(gameInfo.boardWidth) { // base guess on board width
4039                                 case 9:  newVariant = VariantShogi; break;
4040                                 case 10: newVariant = VariantGreat; break;
4041                                 default: newVariant = VariantCrazyhouse; break;
4042                           }
4043                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4044                           /* Get a move list just to see the header, which
4045                              will tell us whether this is really bug or zh */
4046                           if (ics_getting_history == H_FALSE) {
4047                             ics_getting_history = H_REQUESTED;
4048                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4049                             SendToICS(str);
4050                           }
4051                         }
4052                         new_piece[0] = NULLCHAR;
4053                         sscanf(parse, "game %d white [%s black [%s <- %s",
4054                                &gamenum, white_holding, black_holding,
4055                                new_piece);
4056                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4057                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4058                         /* [HGM] copy holdings to board holdings area */
4059                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4060                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4061                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4062 #if ZIPPY
4063                         if (appData.zippyPlay && first.initDone) {
4064                             ZippyHoldings(white_holding, black_holding,
4065                                           new_piece);
4066                         }
4067 #endif /*ZIPPY*/
4068                         if (tinyLayout || smallLayout) {
4069                             char wh[16], bh[16];
4070                             PackHolding(wh, white_holding);
4071                             PackHolding(bh, black_holding);
4072                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4073                                     gameInfo.white, gameInfo.black);
4074                         } else {
4075                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4076                                     gameInfo.white, white_holding, _("vs."),
4077                                     gameInfo.black, black_holding);
4078                         }
4079                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4080                         DrawPosition(FALSE, boards[currentMove]);
4081                         DisplayTitle(str);
4082                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4083                         sscanf(parse, "game %d white [%s black [%s <- %s",
4084                                &gamenum, white_holding, black_holding,
4085                                new_piece);
4086                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4087                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4088                         /* [HGM] copy holdings to partner-board holdings area */
4089                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4090                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4091                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4092                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4093                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4094                       }
4095                     }
4096                     /* Suppress following prompt */
4097                     if (looking_at(buf, &i, "*% ")) {
4098                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4099                         savingComment = FALSE;
4100                         suppressKibitz = 0;
4101                     }
4102                     next_out = i;
4103                 }
4104                 continue;
4105             }
4106
4107             i++;                /* skip unparsed character and loop back */
4108         }
4109
4110         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4111 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4112 //          SendToPlayer(&buf[next_out], i - next_out);
4113             started != STARTED_HOLDINGS && leftover_start > next_out) {
4114             SendToPlayer(&buf[next_out], leftover_start - next_out);
4115             next_out = i;
4116         }
4117
4118         leftover_len = buf_len - leftover_start;
4119         /* if buffer ends with something we couldn't parse,
4120            reparse it after appending the next read */
4121
4122     } else if (count == 0) {
4123         RemoveInputSource(isr);
4124         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4125     } else {
4126         DisplayFatalError(_("Error reading from ICS"), error, 1);
4127     }
4128 }
4129
4130
4131 /* Board style 12 looks like this:
4132
4133    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4134
4135  * The "<12> " is stripped before it gets to this routine.  The two
4136  * trailing 0's (flip state and clock ticking) are later addition, and
4137  * some chess servers may not have them, or may have only the first.
4138  * Additional trailing fields may be added in the future.
4139  */
4140
4141 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4142
4143 #define RELATION_OBSERVING_PLAYED    0
4144 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4145 #define RELATION_PLAYING_MYMOVE      1
4146 #define RELATION_PLAYING_NOTMYMOVE  -1
4147 #define RELATION_EXAMINING           2
4148 #define RELATION_ISOLATED_BOARD     -3
4149 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4150
4151 void
4152 ParseBoard12 (char *string)
4153 {
4154     GameMode newGameMode;
4155     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4156     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4157     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4158     char to_play, board_chars[200];
4159     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4160     char black[32], white[32];
4161     Board board;
4162     int prevMove = currentMove;
4163     int ticking = 2;
4164     ChessMove moveType;
4165     int fromX, fromY, toX, toY;
4166     char promoChar;
4167     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4168     char *bookHit = NULL; // [HGM] book
4169     Boolean weird = FALSE, reqFlag = FALSE;
4170
4171     fromX = fromY = toX = toY = -1;
4172
4173     newGame = FALSE;
4174
4175     if (appData.debugMode)
4176       fprintf(debugFP, _("Parsing board: %s\n"), string);
4177
4178     move_str[0] = NULLCHAR;
4179     elapsed_time[0] = NULLCHAR;
4180     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4181         int  i = 0, j;
4182         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4183             if(string[i] == ' ') { ranks++; files = 0; }
4184             else files++;
4185             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4186             i++;
4187         }
4188         for(j = 0; j <i; j++) board_chars[j] = string[j];
4189         board_chars[i] = '\0';
4190         string += i + 1;
4191     }
4192     n = sscanf(string, PATTERN, &to_play, &double_push,
4193                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4194                &gamenum, white, black, &relation, &basetime, &increment,
4195                &white_stren, &black_stren, &white_time, &black_time,
4196                &moveNum, str, elapsed_time, move_str, &ics_flip,
4197                &ticking);
4198
4199     if (n < 21) {
4200         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4201         DisplayError(str, 0);
4202         return;
4203     }
4204
4205     /* Convert the move number to internal form */
4206     moveNum = (moveNum - 1) * 2;
4207     if (to_play == 'B') moveNum++;
4208     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4209       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4210                         0, 1);
4211       return;
4212     }
4213
4214     switch (relation) {
4215       case RELATION_OBSERVING_PLAYED:
4216       case RELATION_OBSERVING_STATIC:
4217         if (gamenum == -1) {
4218             /* Old ICC buglet */
4219             relation = RELATION_OBSERVING_STATIC;
4220         }
4221         newGameMode = IcsObserving;
4222         break;
4223       case RELATION_PLAYING_MYMOVE:
4224       case RELATION_PLAYING_NOTMYMOVE:
4225         newGameMode =
4226           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4227             IcsPlayingWhite : IcsPlayingBlack;
4228         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4229         break;
4230       case RELATION_EXAMINING:
4231         newGameMode = IcsExamining;
4232         break;
4233       case RELATION_ISOLATED_BOARD:
4234       default:
4235         /* Just display this board.  If user was doing something else,
4236            we will forget about it until the next board comes. */
4237         newGameMode = IcsIdle;
4238         break;
4239       case RELATION_STARTING_POSITION:
4240         newGameMode = gameMode;
4241         break;
4242     }
4243
4244     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4245          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4246       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4247       char *toSqr;
4248       for (k = 0; k < ranks; k++) {
4249         for (j = 0; j < files; j++)
4250           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4251         if(gameInfo.holdingsWidth > 1) {
4252              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4253              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4254         }
4255       }
4256       CopyBoard(partnerBoard, board);
4257       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4258         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4259         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4260       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4261       if(toSqr = strchr(str, '-')) {
4262         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4263         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4264       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4265       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4266       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4267       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4268       if(twoBoards) {
4269           DisplayWhiteClock(white_time, to_play == 'W');
4270           DisplayBlackClock(black_time, to_play != 'W');
4271                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4272       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4273                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4274       DisplayMessage(partnerStatus, "");
4275         partnerBoardValid = TRUE;
4276       return;
4277     }
4278
4279     /* Modify behavior for initial board display on move listing
4280        of wild games.
4281        */
4282     switch (ics_getting_history) {
4283       case H_FALSE:
4284       case H_REQUESTED:
4285         break;
4286       case H_GOT_REQ_HEADER:
4287       case H_GOT_UNREQ_HEADER:
4288         /* This is the initial position of the current game */
4289         gamenum = ics_gamenum;
4290         moveNum = 0;            /* old ICS bug workaround */
4291         if (to_play == 'B') {
4292           startedFromSetupPosition = TRUE;
4293           blackPlaysFirst = TRUE;
4294           moveNum = 1;
4295           if (forwardMostMove == 0) forwardMostMove = 1;
4296           if (backwardMostMove == 0) backwardMostMove = 1;
4297           if (currentMove == 0) currentMove = 1;
4298         }
4299         newGameMode = gameMode;
4300         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4301         break;
4302       case H_GOT_UNWANTED_HEADER:
4303         /* This is an initial board that we don't want */
4304         return;
4305       case H_GETTING_MOVES:
4306         /* Should not happen */
4307         DisplayError(_("Error gathering move list: extra board"), 0);
4308         ics_getting_history = H_FALSE;
4309         return;
4310     }
4311
4312    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4313                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4314      /* [HGM] We seem to have switched variant unexpectedly
4315       * Try to guess new variant from board size
4316       */
4317           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4318           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4319           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4320           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4321           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4322           if(!weird) newVariant = VariantNormal;
4323           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4324           /* Get a move list just to see the header, which
4325              will tell us whether this is really bug or zh */
4326           if (ics_getting_history == H_FALSE) {
4327             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4328             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4329             SendToICS(str);
4330           }
4331     }
4332
4333     /* Take action if this is the first board of a new game, or of a
4334        different game than is currently being displayed.  */
4335     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4336         relation == RELATION_ISOLATED_BOARD) {
4337
4338         /* Forget the old game and get the history (if any) of the new one */
4339         if (gameMode != BeginningOfGame) {
4340           Reset(TRUE, TRUE);
4341         }
4342         newGame = TRUE;
4343         if (appData.autoRaiseBoard) BoardToTop();
4344         prevMove = -3;
4345         if (gamenum == -1) {
4346             newGameMode = IcsIdle;
4347         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4348                    appData.getMoveList && !reqFlag) {
4349             /* Need to get game history */
4350             ics_getting_history = H_REQUESTED;
4351             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4352             SendToICS(str);
4353         }
4354
4355         /* Initially flip the board to have black on the bottom if playing
4356            black or if the ICS flip flag is set, but let the user change
4357            it with the Flip View button. */
4358         flipView = appData.autoFlipView ?
4359           (newGameMode == IcsPlayingBlack) || ics_flip :
4360           appData.flipView;
4361
4362         /* Done with values from previous mode; copy in new ones */
4363         gameMode = newGameMode;
4364         ModeHighlight();
4365         ics_gamenum = gamenum;
4366         if (gamenum == gs_gamenum) {
4367             int klen = strlen(gs_kind);
4368             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4369             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4370             gameInfo.event = StrSave(str);
4371         } else {
4372             gameInfo.event = StrSave("ICS game");
4373         }
4374         gameInfo.site = StrSave(appData.icsHost);
4375         gameInfo.date = PGNDate();
4376         gameInfo.round = StrSave("-");
4377         gameInfo.white = StrSave(white);
4378         gameInfo.black = StrSave(black);
4379         timeControl = basetime * 60 * 1000;
4380         timeControl_2 = 0;
4381         timeIncrement = increment * 1000;
4382         movesPerSession = 0;
4383         gameInfo.timeControl = TimeControlTagValue();
4384         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4385   if (appData.debugMode) {
4386     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4387     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4388     setbuf(debugFP, NULL);
4389   }
4390
4391         gameInfo.outOfBook = NULL;
4392
4393         /* Do we have the ratings? */
4394         if (strcmp(player1Name, white) == 0 &&
4395             strcmp(player2Name, black) == 0) {
4396             if (appData.debugMode)
4397               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4398                       player1Rating, player2Rating);
4399             gameInfo.whiteRating = player1Rating;
4400             gameInfo.blackRating = player2Rating;
4401         } else if (strcmp(player2Name, white) == 0 &&
4402                    strcmp(player1Name, black) == 0) {
4403             if (appData.debugMode)
4404               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4405                       player2Rating, player1Rating);
4406             gameInfo.whiteRating = player2Rating;
4407             gameInfo.blackRating = player1Rating;
4408         }
4409         player1Name[0] = player2Name[0] = NULLCHAR;
4410
4411         /* Silence shouts if requested */
4412         if (appData.quietPlay &&
4413             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4414             SendToICS(ics_prefix);
4415             SendToICS("set shout 0\n");
4416         }
4417     }
4418
4419     /* Deal with midgame name changes */
4420     if (!newGame) {
4421         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4422             if (gameInfo.white) free(gameInfo.white);
4423             gameInfo.white = StrSave(white);
4424         }
4425         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4426             if (gameInfo.black) free(gameInfo.black);
4427             gameInfo.black = StrSave(black);
4428         }
4429     }
4430
4431     /* Throw away game result if anything actually changes in examine mode */
4432     if (gameMode == IcsExamining && !newGame) {
4433         gameInfo.result = GameUnfinished;
4434         if (gameInfo.resultDetails != NULL) {
4435             free(gameInfo.resultDetails);
4436             gameInfo.resultDetails = NULL;
4437         }
4438     }
4439
4440     /* In pausing && IcsExamining mode, we ignore boards coming
4441        in if they are in a different variation than we are. */
4442     if (pauseExamInvalid) return;
4443     if (pausing && gameMode == IcsExamining) {
4444         if (moveNum <= pauseExamForwardMostMove) {
4445             pauseExamInvalid = TRUE;
4446             forwardMostMove = pauseExamForwardMostMove;
4447             return;
4448         }
4449     }
4450
4451   if (appData.debugMode) {
4452     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4453   }
4454     /* Parse the board */
4455     for (k = 0; k < ranks; k++) {
4456       for (j = 0; j < files; j++)
4457         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4458       if(gameInfo.holdingsWidth > 1) {
4459            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4460            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4461       }
4462     }
4463     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4464       board[5][BOARD_RGHT+1] = WhiteAngel;
4465       board[6][BOARD_RGHT+1] = WhiteMarshall;
4466       board[1][0] = BlackMarshall;
4467       board[2][0] = BlackAngel;
4468       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4469     }
4470     CopyBoard(boards[moveNum], board);
4471     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4472     if (moveNum == 0) {
4473         startedFromSetupPosition =
4474           !CompareBoards(board, initialPosition);
4475         if(startedFromSetupPosition)
4476             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4477     }
4478
4479     /* [HGM] Set castling rights. Take the outermost Rooks,
4480        to make it also work for FRC opening positions. Note that board12
4481        is really defective for later FRC positions, as it has no way to
4482        indicate which Rook can castle if they are on the same side of King.
4483        For the initial position we grant rights to the outermost Rooks,
4484        and remember thos rights, and we then copy them on positions
4485        later in an FRC game. This means WB might not recognize castlings with
4486        Rooks that have moved back to their original position as illegal,
4487        but in ICS mode that is not its job anyway.
4488     */
4489     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4490     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4491
4492         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4493             if(board[0][i] == WhiteRook) j = i;
4494         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4495         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4496             if(board[0][i] == WhiteRook) j = i;
4497         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4498         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4499             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4500         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4501         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4502             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4503         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4504
4505         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4506         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4507         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4508             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4509         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4510             if(board[BOARD_HEIGHT-1][k] == bKing)
4511                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4512         if(gameInfo.variant == VariantTwoKings) {
4513             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4514             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4515             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4516         }
4517     } else { int r;
4518         r = boards[moveNum][CASTLING][0] = initialRights[0];
4519         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4520         r = boards[moveNum][CASTLING][1] = initialRights[1];
4521         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4522         r = boards[moveNum][CASTLING][3] = initialRights[3];
4523         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4524         r = boards[moveNum][CASTLING][4] = initialRights[4];
4525         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4526         /* wildcastle kludge: always assume King has rights */
4527         r = boards[moveNum][CASTLING][2] = initialRights[2];
4528         r = boards[moveNum][CASTLING][5] = initialRights[5];
4529     }
4530     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4531     boards[moveNum][EP_STATUS] = EP_NONE;
4532     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4533     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4534     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4535
4536
4537     if (ics_getting_history == H_GOT_REQ_HEADER ||
4538         ics_getting_history == H_GOT_UNREQ_HEADER) {
4539         /* This was an initial position from a move list, not
4540            the current position */
4541         return;
4542     }
4543
4544     /* Update currentMove and known move number limits */
4545     newMove = newGame || moveNum > forwardMostMove;
4546
4547     if (newGame) {
4548         forwardMostMove = backwardMostMove = currentMove = moveNum;
4549         if (gameMode == IcsExamining && moveNum == 0) {
4550           /* Workaround for ICS limitation: we are not told the wild
4551              type when starting to examine a game.  But if we ask for
4552              the move list, the move list header will tell us */
4553             ics_getting_history = H_REQUESTED;
4554             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4555             SendToICS(str);
4556         }
4557     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4558                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4559 #if ZIPPY
4560         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4561         /* [HGM] applied this also to an engine that is silently watching        */
4562         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4563             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4564             gameInfo.variant == currentlyInitializedVariant) {
4565           takeback = forwardMostMove - moveNum;
4566           for (i = 0; i < takeback; i++) {
4567             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4568             SendToProgram("undo\n", &first);
4569           }
4570         }
4571 #endif
4572
4573         forwardMostMove = moveNum;
4574         if (!pausing || currentMove > forwardMostMove)
4575           currentMove = forwardMostMove;
4576     } else {
4577         /* New part of history that is not contiguous with old part */
4578         if (pausing && gameMode == IcsExamining) {
4579             pauseExamInvalid = TRUE;
4580             forwardMostMove = pauseExamForwardMostMove;
4581             return;
4582         }
4583         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4584 #if ZIPPY
4585             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4586                 // [HGM] when we will receive the move list we now request, it will be
4587                 // fed to the engine from the first move on. So if the engine is not
4588                 // in the initial position now, bring it there.
4589                 InitChessProgram(&first, 0);
4590             }
4591 #endif
4592             ics_getting_history = H_REQUESTED;
4593             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4594             SendToICS(str);
4595         }
4596         forwardMostMove = backwardMostMove = currentMove = moveNum;
4597     }
4598
4599     /* Update the clocks */
4600     if (strchr(elapsed_time, '.')) {
4601       /* Time is in ms */
4602       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4603       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4604     } else {
4605       /* Time is in seconds */
4606       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4607       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4608     }
4609
4610
4611 #if ZIPPY
4612     if (appData.zippyPlay && newGame &&
4613         gameMode != IcsObserving && gameMode != IcsIdle &&
4614         gameMode != IcsExamining)
4615       ZippyFirstBoard(moveNum, basetime, increment);
4616 #endif
4617
4618     /* Put the move on the move list, first converting
4619        to canonical algebraic form. */
4620     if (moveNum > 0) {
4621   if (appData.debugMode) {
4622     if (appData.debugMode) { int f = forwardMostMove;
4623         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4624                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4625                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4626     }
4627     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4628     fprintf(debugFP, "moveNum = %d\n", moveNum);
4629     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4630     setbuf(debugFP, NULL);
4631   }
4632         if (moveNum <= backwardMostMove) {
4633             /* We don't know what the board looked like before
4634                this move.  Punt. */
4635           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4636             strcat(parseList[moveNum - 1], " ");
4637             strcat(parseList[moveNum - 1], elapsed_time);
4638             moveList[moveNum - 1][0] = NULLCHAR;
4639         } else if (strcmp(move_str, "none") == 0) {
4640             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4641             /* Again, we don't know what the board looked like;
4642                this is really the start of the game. */
4643             parseList[moveNum - 1][0] = NULLCHAR;
4644             moveList[moveNum - 1][0] = NULLCHAR;
4645             backwardMostMove = moveNum;
4646             startedFromSetupPosition = TRUE;
4647             fromX = fromY = toX = toY = -1;
4648         } else {
4649           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4650           //                 So we parse the long-algebraic move string in stead of the SAN move
4651           int valid; char buf[MSG_SIZ], *prom;
4652
4653           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4654                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4655           // str looks something like "Q/a1-a2"; kill the slash
4656           if(str[1] == '/')
4657             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4658           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4659           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4660                 strcat(buf, prom); // long move lacks promo specification!
4661           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4662                 if(appData.debugMode)
4663                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4664                 safeStrCpy(move_str, buf, MSG_SIZ);
4665           }
4666           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4667                                 &fromX, &fromY, &toX, &toY, &promoChar)
4668                || ParseOneMove(buf, moveNum - 1, &moveType,
4669                                 &fromX, &fromY, &toX, &toY, &promoChar);
4670           // end of long SAN patch
4671           if (valid) {
4672             (void) CoordsToAlgebraic(boards[moveNum - 1],
4673                                      PosFlags(moveNum - 1),
4674                                      fromY, fromX, toY, toX, promoChar,
4675                                      parseList[moveNum-1]);
4676             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4677               case MT_NONE:
4678               case MT_STALEMATE:
4679               default:
4680                 break;
4681               case MT_CHECK:
4682                 if(gameInfo.variant != VariantShogi)
4683                     strcat(parseList[moveNum - 1], "+");
4684                 break;
4685               case MT_CHECKMATE:
4686               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4687                 strcat(parseList[moveNum - 1], "#");
4688                 break;
4689             }
4690             strcat(parseList[moveNum - 1], " ");
4691             strcat(parseList[moveNum - 1], elapsed_time);
4692             /* currentMoveString is set as a side-effect of ParseOneMove */
4693             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4694             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4695             strcat(moveList[moveNum - 1], "\n");
4696
4697             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4698                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4699               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4700                 ChessSquare old, new = boards[moveNum][k][j];
4701                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4702                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4703                   if(old == new) continue;
4704                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4705                   else if(new == WhiteWazir || new == BlackWazir) {
4706                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4707                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4708                       else boards[moveNum][k][j] = old; // preserve type of Gold
4709                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4710                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4711               }
4712           } else {
4713             /* Move from ICS was illegal!?  Punt. */
4714             if (appData.debugMode) {
4715               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4716               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4717             }
4718             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4719             strcat(parseList[moveNum - 1], " ");
4720             strcat(parseList[moveNum - 1], elapsed_time);
4721             moveList[moveNum - 1][0] = NULLCHAR;
4722             fromX = fromY = toX = toY = -1;
4723           }
4724         }
4725   if (appData.debugMode) {
4726     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4727     setbuf(debugFP, NULL);
4728   }
4729
4730 #if ZIPPY
4731         /* Send move to chess program (BEFORE animating it). */
4732         if (appData.zippyPlay && !newGame && newMove &&
4733            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4734
4735             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4736                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4737                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4738                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4739                             move_str);
4740                     DisplayError(str, 0);
4741                 } else {
4742                     if (first.sendTime) {
4743                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4744                     }
4745                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4746                     if (firstMove && !bookHit) {
4747                         firstMove = FALSE;
4748                         if (first.useColors) {
4749                           SendToProgram(gameMode == IcsPlayingWhite ?
4750                                         "white\ngo\n" :
4751                                         "black\ngo\n", &first);
4752                         } else {
4753                           SendToProgram("go\n", &first);
4754                         }
4755                         first.maybeThinking = TRUE;
4756                     }
4757                 }
4758             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4759               if (moveList[moveNum - 1][0] == NULLCHAR) {
4760                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4761                 DisplayError(str, 0);
4762               } else {
4763                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4764                 SendMoveToProgram(moveNum - 1, &first);
4765               }
4766             }
4767         }
4768 #endif
4769     }
4770
4771     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4772         /* If move comes from a remote source, animate it.  If it
4773            isn't remote, it will have already been animated. */
4774         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4775             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4776         }
4777         if (!pausing && appData.highlightLastMove) {
4778             SetHighlights(fromX, fromY, toX, toY);
4779         }
4780     }
4781
4782     /* Start the clocks */
4783     whiteFlag = blackFlag = FALSE;
4784     appData.clockMode = !(basetime == 0 && increment == 0);
4785     if (ticking == 0) {
4786       ics_clock_paused = TRUE;
4787       StopClocks();
4788     } else if (ticking == 1) {
4789       ics_clock_paused = FALSE;
4790     }
4791     if (gameMode == IcsIdle ||
4792         relation == RELATION_OBSERVING_STATIC ||
4793         relation == RELATION_EXAMINING ||
4794         ics_clock_paused)
4795       DisplayBothClocks();
4796     else
4797       StartClocks();
4798
4799     /* Display opponents and material strengths */
4800     if (gameInfo.variant != VariantBughouse &&
4801         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4802         if (tinyLayout || smallLayout) {
4803             if(gameInfo.variant == VariantNormal)
4804               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4805                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4806                     basetime, increment);
4807             else
4808               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4809                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4810                     basetime, increment, (int) gameInfo.variant);
4811         } else {
4812             if(gameInfo.variant == VariantNormal)
4813               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4814                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4815                     basetime, increment);
4816             else
4817               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4818                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4819                     basetime, increment, VariantName(gameInfo.variant));
4820         }
4821         DisplayTitle(str);
4822   if (appData.debugMode) {
4823     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4824   }
4825     }
4826
4827
4828     /* Display the board */
4829     if (!pausing && !appData.noGUI) {
4830
4831       if (appData.premove)
4832           if (!gotPremove ||
4833              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4834              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4835               ClearPremoveHighlights();
4836
4837       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4838         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4839       DrawPosition(j, boards[currentMove]);
4840
4841       DisplayMove(moveNum - 1);
4842       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4843             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4844               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4845         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4846       }
4847     }
4848
4849     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4850 #if ZIPPY
4851     if(bookHit) { // [HGM] book: simulate book reply
4852         static char bookMove[MSG_SIZ]; // a bit generous?
4853
4854         programStats.nodes = programStats.depth = programStats.time =
4855         programStats.score = programStats.got_only_move = 0;
4856         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4857
4858         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4859         strcat(bookMove, bookHit);
4860         HandleMachineMove(bookMove, &first);
4861     }
4862 #endif
4863 }
4864
4865 void
4866 GetMoveListEvent ()
4867 {
4868     char buf[MSG_SIZ];
4869     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4870         ics_getting_history = H_REQUESTED;
4871         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4872         SendToICS(buf);
4873     }
4874 }
4875
4876 void
4877 AnalysisPeriodicEvent (int force)
4878 {
4879     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4880          && !force) || !appData.periodicUpdates)
4881       return;
4882
4883     /* Send . command to Crafty to collect stats */
4884     SendToProgram(".\n", &first);
4885
4886     /* Don't send another until we get a response (this makes
4887        us stop sending to old Crafty's which don't understand
4888        the "." command (sending illegal cmds resets node count & time,
4889        which looks bad)) */
4890     programStats.ok_to_send = 0;
4891 }
4892
4893 void
4894 ics_update_width (int new_width)
4895 {
4896         ics_printf("set width %d\n", new_width);
4897 }
4898
4899 void
4900 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4901 {
4902     char buf[MSG_SIZ];
4903
4904     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4905         // null move in variant where engine does not understand it (for analysis purposes)
4906         SendBoard(cps, moveNum + 1); // send position after move in stead.
4907         return;
4908     }
4909     if (cps->useUsermove) {
4910       SendToProgram("usermove ", cps);
4911     }
4912     if (cps->useSAN) {
4913       char *space;
4914       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4915         int len = space - parseList[moveNum];
4916         memcpy(buf, parseList[moveNum], len);
4917         buf[len++] = '\n';
4918         buf[len] = NULLCHAR;
4919       } else {
4920         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4921       }
4922       SendToProgram(buf, cps);
4923     } else {
4924       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4925         AlphaRank(moveList[moveNum], 4);
4926         SendToProgram(moveList[moveNum], cps);
4927         AlphaRank(moveList[moveNum], 4); // and back
4928       } else
4929       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4930        * the engine. It would be nice to have a better way to identify castle
4931        * moves here. */
4932       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4933                                                                          && cps->useOOCastle) {
4934         int fromX = moveList[moveNum][0] - AAA;
4935         int fromY = moveList[moveNum][1] - ONE;
4936         int toX = moveList[moveNum][2] - AAA;
4937         int toY = moveList[moveNum][3] - ONE;
4938         if((boards[moveNum][fromY][fromX] == WhiteKing
4939             && boards[moveNum][toY][toX] == WhiteRook)
4940            || (boards[moveNum][fromY][fromX] == BlackKing
4941                && boards[moveNum][toY][toX] == BlackRook)) {
4942           if(toX > fromX) SendToProgram("O-O\n", cps);
4943           else SendToProgram("O-O-O\n", cps);
4944         }
4945         else SendToProgram(moveList[moveNum], cps);
4946       } else
4947       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4948         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4949           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4950           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4951                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4952         } else
4953           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4954                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4955         SendToProgram(buf, cps);
4956       }
4957       else SendToProgram(moveList[moveNum], cps);
4958       /* End of additions by Tord */
4959     }
4960
4961     /* [HGM] setting up the opening has brought engine in force mode! */
4962     /*       Send 'go' if we are in a mode where machine should play. */
4963     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4964         (gameMode == TwoMachinesPlay   ||
4965 #if ZIPPY
4966          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4967 #endif
4968          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4969         SendToProgram("go\n", cps);
4970   if (appData.debugMode) {
4971     fprintf(debugFP, "(extra)\n");
4972   }
4973     }
4974     setboardSpoiledMachineBlack = 0;
4975 }
4976
4977 void
4978 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4979 {
4980     char user_move[MSG_SIZ];
4981     char suffix[4];
4982
4983     if(gameInfo.variant == VariantSChess && promoChar) {
4984         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4985         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4986     } else suffix[0] = NULLCHAR;
4987
4988     switch (moveType) {
4989       default:
4990         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4991                 (int)moveType, fromX, fromY, toX, toY);
4992         DisplayError(user_move + strlen("say "), 0);
4993         break;
4994       case WhiteKingSideCastle:
4995       case BlackKingSideCastle:
4996       case WhiteQueenSideCastleWild:
4997       case BlackQueenSideCastleWild:
4998       /* PUSH Fabien */
4999       case WhiteHSideCastleFR:
5000       case BlackHSideCastleFR:
5001       /* POP Fabien */
5002         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5003         break;
5004       case WhiteQueenSideCastle:
5005       case BlackQueenSideCastle:
5006       case WhiteKingSideCastleWild:
5007       case BlackKingSideCastleWild:
5008       /* PUSH Fabien */
5009       case WhiteASideCastleFR:
5010       case BlackASideCastleFR:
5011       /* POP Fabien */
5012         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5013         break;
5014       case WhiteNonPromotion:
5015       case BlackNonPromotion:
5016         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5017         break;
5018       case WhitePromotion:
5019       case BlackPromotion:
5020         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5021           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5022                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5023                 PieceToChar(WhiteFerz));
5024         else if(gameInfo.variant == VariantGreat)
5025           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5026                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5027                 PieceToChar(WhiteMan));
5028         else
5029           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5030                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5031                 promoChar);
5032         break;
5033       case WhiteDrop:
5034       case BlackDrop:
5035       drop:
5036         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5037                  ToUpper(PieceToChar((ChessSquare) fromX)),
5038                  AAA + toX, ONE + toY);
5039         break;
5040       case IllegalMove:  /* could be a variant we don't quite understand */
5041         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5042       case NormalMove:
5043       case WhiteCapturesEnPassant:
5044       case BlackCapturesEnPassant:
5045         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5046                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5047         break;
5048     }
5049     SendToICS(user_move);
5050     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5051         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5052 }
5053
5054 void
5055 UploadGameEvent ()
5056 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5057     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5058     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5059     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5060       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5061       return;
5062     }
5063     if(gameMode != IcsExamining) { // is this ever not the case?
5064         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5065
5066         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5067           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5068         } else { // on FICS we must first go to general examine mode
5069           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5070         }
5071         if(gameInfo.variant != VariantNormal) {
5072             // try figure out wild number, as xboard names are not always valid on ICS
5073             for(i=1; i<=36; i++) {
5074               snprintf(buf, MSG_SIZ, "wild/%d", i);
5075                 if(StringToVariant(buf) == gameInfo.variant) break;
5076             }
5077             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5078             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5079             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5080         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5081         SendToICS(ics_prefix);
5082         SendToICS(buf);
5083         if(startedFromSetupPosition || backwardMostMove != 0) {
5084           fen = PositionToFEN(backwardMostMove, NULL);
5085           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5086             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5087             SendToICS(buf);
5088           } else { // FICS: everything has to set by separate bsetup commands
5089             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5090             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5091             SendToICS(buf);
5092             if(!WhiteOnMove(backwardMostMove)) {
5093                 SendToICS("bsetup tomove black\n");
5094             }
5095             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5096             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5097             SendToICS(buf);
5098             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5099             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5100             SendToICS(buf);
5101             i = boards[backwardMostMove][EP_STATUS];
5102             if(i >= 0) { // set e.p.
5103               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5104                 SendToICS(buf);
5105             }
5106             bsetup++;
5107           }
5108         }
5109       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5110             SendToICS("bsetup done\n"); // switch to normal examining.
5111     }
5112     for(i = backwardMostMove; i<last; i++) {
5113         char buf[20];
5114         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5115         SendToICS(buf);
5116     }
5117     SendToICS(ics_prefix);
5118     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5119 }
5120
5121 void
5122 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5123 {
5124     if (rf == DROP_RANK) {
5125       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5126       sprintf(move, "%c@%c%c\n",
5127                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5128     } else {
5129         if (promoChar == 'x' || promoChar == NULLCHAR) {
5130           sprintf(move, "%c%c%c%c\n",
5131                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5132         } else {
5133             sprintf(move, "%c%c%c%c%c\n",
5134                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5135         }
5136     }
5137 }
5138
5139 void
5140 ProcessICSInitScript (FILE *f)
5141 {
5142     char buf[MSG_SIZ];
5143
5144     while (fgets(buf, MSG_SIZ, f)) {
5145         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5146     }
5147
5148     fclose(f);
5149 }
5150
5151
5152 static int lastX, lastY, selectFlag, dragging;
5153
5154 void
5155 Sweep (int step)
5156 {
5157     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5158     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5159     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5160     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5161     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5162     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5163     do {
5164         promoSweep -= step;
5165         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5166         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5167         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5168         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5169         if(!step) step = -1;
5170     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5171             appData.testLegality && (promoSweep == king ||
5172             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5173     ChangeDragPiece(promoSweep);
5174 }
5175
5176 int
5177 PromoScroll (int x, int y)
5178 {
5179   int step = 0;
5180
5181   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5182   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5183   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5184   if(!step) return FALSE;
5185   lastX = x; lastY = y;
5186   if((promoSweep < BlackPawn) == flipView) step = -step;
5187   if(step > 0) selectFlag = 1;
5188   if(!selectFlag) Sweep(step);
5189   return FALSE;
5190 }
5191
5192 void
5193 NextPiece (int step)
5194 {
5195     ChessSquare piece = boards[currentMove][toY][toX];
5196     do {
5197         pieceSweep -= step;
5198         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5199         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5200         if(!step) step = -1;
5201     } while(PieceToChar(pieceSweep) == '.');
5202     boards[currentMove][toY][toX] = pieceSweep;
5203     DrawPosition(FALSE, boards[currentMove]);
5204     boards[currentMove][toY][toX] = piece;
5205 }
5206 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5207 void
5208 AlphaRank (char *move, int n)
5209 {
5210 //    char *p = move, c; int x, y;
5211
5212     if (appData.debugMode) {
5213         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5214     }
5215
5216     if(move[1]=='*' &&
5217        move[2]>='0' && move[2]<='9' &&
5218        move[3]>='a' && move[3]<='x'    ) {
5219         move[1] = '@';
5220         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5221         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5222     } else
5223     if(move[0]>='0' && move[0]<='9' &&
5224        move[1]>='a' && move[1]<='x' &&
5225        move[2]>='0' && move[2]<='9' &&
5226        move[3]>='a' && move[3]<='x'    ) {
5227         /* input move, Shogi -> normal */
5228         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5229         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5230         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5231         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5232     } else
5233     if(move[1]=='@' &&
5234        move[3]>='0' && move[3]<='9' &&
5235        move[2]>='a' && move[2]<='x'    ) {
5236         move[1] = '*';
5237         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5238         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5239     } else
5240     if(
5241        move[0]>='a' && move[0]<='x' &&
5242        move[3]>='0' && move[3]<='9' &&
5243        move[2]>='a' && move[2]<='x'    ) {
5244          /* output move, normal -> Shogi */
5245         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5246         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5247         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5248         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5249         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5250     }
5251     if (appData.debugMode) {
5252         fprintf(debugFP, "   out = '%s'\n", move);
5253     }
5254 }
5255
5256 char yy_textstr[8000];
5257
5258 /* Parser for moves from gnuchess, ICS, or user typein box */
5259 Boolean
5260 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5261 {
5262     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5263
5264     switch (*moveType) {
5265       case WhitePromotion:
5266       case BlackPromotion:
5267       case WhiteNonPromotion:
5268       case BlackNonPromotion:
5269       case NormalMove:
5270       case WhiteCapturesEnPassant:
5271       case BlackCapturesEnPassant:
5272       case WhiteKingSideCastle:
5273       case WhiteQueenSideCastle:
5274       case BlackKingSideCastle:
5275       case BlackQueenSideCastle:
5276       case WhiteKingSideCastleWild:
5277       case WhiteQueenSideCastleWild:
5278       case BlackKingSideCastleWild:
5279       case BlackQueenSideCastleWild:
5280       /* Code added by Tord: */
5281       case WhiteHSideCastleFR:
5282       case WhiteASideCastleFR:
5283       case BlackHSideCastleFR:
5284       case BlackASideCastleFR:
5285       /* End of code added by Tord */
5286       case IllegalMove:         /* bug or odd chess variant */
5287         *fromX = currentMoveString[0] - AAA;
5288         *fromY = currentMoveString[1] - ONE;
5289         *toX = currentMoveString[2] - AAA;
5290         *toY = currentMoveString[3] - ONE;
5291         *promoChar = currentMoveString[4];
5292         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5293             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5294     if (appData.debugMode) {
5295         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5296     }
5297             *fromX = *fromY = *toX = *toY = 0;
5298             return FALSE;
5299         }
5300         if (appData.testLegality) {
5301           return (*moveType != IllegalMove);
5302         } else {
5303           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5304                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5305         }
5306
5307       case WhiteDrop:
5308       case BlackDrop:
5309         *fromX = *moveType == WhiteDrop ?
5310           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5311           (int) CharToPiece(ToLower(currentMoveString[0]));
5312         *fromY = DROP_RANK;
5313         *toX = currentMoveString[2] - AAA;
5314         *toY = currentMoveString[3] - ONE;
5315         *promoChar = NULLCHAR;
5316         return TRUE;
5317
5318       case AmbiguousMove:
5319       case ImpossibleMove:
5320       case EndOfFile:
5321       case ElapsedTime:
5322       case Comment:
5323       case PGNTag:
5324       case NAG:
5325       case WhiteWins:
5326       case BlackWins:
5327       case GameIsDrawn:
5328       default:
5329     if (appData.debugMode) {
5330         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5331     }
5332         /* bug? */
5333         *fromX = *fromY = *toX = *toY = 0;
5334         *promoChar = NULLCHAR;
5335         return FALSE;
5336     }
5337 }
5338
5339 Boolean pushed = FALSE;
5340 char *lastParseAttempt;
5341
5342 void
5343 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5344 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5345   int fromX, fromY, toX, toY; char promoChar;
5346   ChessMove moveType;
5347   Boolean valid;
5348   int nr = 0;
5349
5350   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5351     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5352     pushed = TRUE;
5353   }
5354   endPV = forwardMostMove;
5355   do {
5356     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5357     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5358     lastParseAttempt = pv;
5359     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5360     if(!valid && nr == 0 &&
5361        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5362         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5363         // Hande case where played move is different from leading PV move
5364         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5365         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5366         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5367         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5368           endPV += 2; // if position different, keep this
5369           moveList[endPV-1][0] = fromX + AAA;
5370           moveList[endPV-1][1] = fromY + ONE;
5371           moveList[endPV-1][2] = toX + AAA;
5372           moveList[endPV-1][3] = toY + ONE;
5373           parseList[endPV-1][0] = NULLCHAR;
5374           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5375         }
5376       }
5377     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5378     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5379     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5380     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5381         valid++; // allow comments in PV
5382         continue;
5383     }
5384     nr++;
5385     if(endPV+1 > framePtr) break; // no space, truncate
5386     if(!valid) break;
5387     endPV++;
5388     CopyBoard(boards[endPV], boards[endPV-1]);
5389     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5390     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5391     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5392     CoordsToAlgebraic(boards[endPV - 1],
5393                              PosFlags(endPV - 1),
5394                              fromY, fromX, toY, toX, promoChar,
5395                              parseList[endPV - 1]);
5396   } while(valid);
5397   if(atEnd == 2) return; // used hidden, for PV conversion
5398   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5399   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5400   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5401                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5402   DrawPosition(TRUE, boards[currentMove]);
5403 }
5404
5405 int
5406 MultiPV (ChessProgramState *cps)
5407 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5408         int i;
5409         for(i=0; i<cps->nrOptions; i++)
5410             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5411                 return i;
5412         return -1;
5413 }
5414
5415 Boolean
5416 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5417 {
5418         int startPV, multi, lineStart, origIndex = index;
5419         char *p, buf2[MSG_SIZ];
5420
5421         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5422         lastX = x; lastY = y;
5423         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5424         lineStart = startPV = index;
5425         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5426         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5427         index = startPV;
5428         do{ while(buf[index] && buf[index] != '\n') index++;
5429         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5430         buf[index] = 0;
5431         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5432                 int n = first.option[multi].value;
5433                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5434                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5435                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5436                 first.option[multi].value = n;
5437                 *start = *end = 0;
5438                 return FALSE;
5439         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5440                 ExcludeClick(origIndex - lineStart);
5441                 return FALSE;
5442         }
5443         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5444         *start = startPV; *end = index-1;
5445         return TRUE;
5446 }
5447
5448 char *
5449 PvToSAN (char *pv)
5450 {
5451         static char buf[10*MSG_SIZ];
5452         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5453         *buf = NULLCHAR;
5454         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5455         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5456         for(i = forwardMostMove; i<endPV; i++){
5457             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5458             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5459             k += strlen(buf+k);
5460         }
5461         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5462         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5463         endPV = savedEnd;
5464         return buf;
5465 }
5466
5467 Boolean
5468 LoadPV (int x, int y)
5469 { // called on right mouse click to load PV
5470   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5471   lastX = x; lastY = y;
5472   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5473   return TRUE;
5474 }
5475
5476 void
5477 UnLoadPV ()
5478 {
5479   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5480   if(endPV < 0) return;
5481   if(appData.autoCopyPV) CopyFENToClipboard();
5482   endPV = -1;
5483   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5484         Boolean saveAnimate = appData.animate;
5485         if(pushed) {
5486             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5487                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5488             } else storedGames--; // abandon shelved tail of original game
5489         }
5490         pushed = FALSE;
5491         forwardMostMove = currentMove;
5492         currentMove = oldFMM;
5493         appData.animate = FALSE;
5494         ToNrEvent(forwardMostMove);
5495         appData.animate = saveAnimate;
5496   }
5497   currentMove = forwardMostMove;
5498   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5499   ClearPremoveHighlights();
5500   DrawPosition(TRUE, boards[currentMove]);
5501 }
5502
5503 void
5504 MovePV (int x, int y, int h)
5505 { // step through PV based on mouse coordinates (called on mouse move)
5506   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5507
5508   // we must somehow check if right button is still down (might be released off board!)
5509   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5510   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5511   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5512   if(!step) return;
5513   lastX = x; lastY = y;
5514
5515   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5516   if(endPV < 0) return;
5517   if(y < margin) step = 1; else
5518   if(y > h - margin) step = -1;
5519   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5520   currentMove += step;
5521   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5522   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5523                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5524   DrawPosition(FALSE, boards[currentMove]);
5525 }
5526
5527
5528 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5529 // All positions will have equal probability, but the current method will not provide a unique
5530 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5531 #define DARK 1
5532 #define LITE 2
5533 #define ANY 3
5534
5535 int squaresLeft[4];
5536 int piecesLeft[(int)BlackPawn];
5537 int seed, nrOfShuffles;
5538
5539 void
5540 GetPositionNumber ()
5541 {       // sets global variable seed
5542         int i;
5543
5544         seed = appData.defaultFrcPosition;
5545         if(seed < 0) { // randomize based on time for negative FRC position numbers
5546                 for(i=0; i<50; i++) seed += random();
5547                 seed = random() ^ random() >> 8 ^ random() << 8;
5548                 if(seed<0) seed = -seed;
5549         }
5550 }
5551
5552 int
5553 put (Board board, int pieceType, int rank, int n, int shade)
5554 // put the piece on the (n-1)-th empty squares of the given shade
5555 {
5556         int i;
5557
5558         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5559                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5560                         board[rank][i] = (ChessSquare) pieceType;
5561                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5562                         squaresLeft[ANY]--;
5563                         piecesLeft[pieceType]--;
5564                         return i;
5565                 }
5566         }
5567         return -1;
5568 }
5569
5570
5571 void
5572 AddOnePiece (Board board, int pieceType, int rank, int shade)
5573 // calculate where the next piece goes, (any empty square), and put it there
5574 {
5575         int i;
5576
5577         i = seed % squaresLeft[shade];
5578         nrOfShuffles *= squaresLeft[shade];
5579         seed /= squaresLeft[shade];
5580         put(board, pieceType, rank, i, shade);
5581 }
5582
5583 void
5584 AddTwoPieces (Board board, int pieceType, int rank)
5585 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5586 {
5587         int i, n=squaresLeft[ANY], j=n-1, k;
5588
5589         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5590         i = seed % k;  // pick one
5591         nrOfShuffles *= k;
5592         seed /= k;
5593         while(i >= j) i -= j--;
5594         j = n - 1 - j; i += j;
5595         put(board, pieceType, rank, j, ANY);
5596         put(board, pieceType, rank, i, ANY);
5597 }
5598
5599 void
5600 SetUpShuffle (Board board, int number)
5601 {
5602         int i, p, first=1;
5603
5604         GetPositionNumber(); nrOfShuffles = 1;
5605
5606         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5607         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5608         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5609
5610         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5611
5612         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5613             p = (int) board[0][i];
5614             if(p < (int) BlackPawn) piecesLeft[p] ++;
5615             board[0][i] = EmptySquare;
5616         }
5617
5618         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5619             // shuffles restricted to allow normal castling put KRR first
5620             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5621                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5622             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5623                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5624             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5625                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5626             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5627                 put(board, WhiteRook, 0, 0, ANY);
5628             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5629         }
5630
5631         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5632             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5633             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5634                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5635                 while(piecesLeft[p] >= 2) {
5636                     AddOnePiece(board, p, 0, LITE);
5637                     AddOnePiece(board, p, 0, DARK);
5638                 }
5639                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5640             }
5641
5642         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5643             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5644             // but we leave King and Rooks for last, to possibly obey FRC restriction
5645             if(p == (int)WhiteRook) continue;
5646             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5647             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5648         }
5649
5650         // now everything is placed, except perhaps King (Unicorn) and Rooks
5651
5652         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5653             // Last King gets castling rights
5654             while(piecesLeft[(int)WhiteUnicorn]) {
5655                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5656                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5657             }
5658
5659             while(piecesLeft[(int)WhiteKing]) {
5660                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5661                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5662             }
5663
5664
5665         } else {
5666             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5667             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5668         }
5669
5670         // Only Rooks can be left; simply place them all
5671         while(piecesLeft[(int)WhiteRook]) {
5672                 i = put(board, WhiteRook, 0, 0, ANY);
5673                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5674                         if(first) {
5675                                 first=0;
5676                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5677                         }
5678                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5679                 }
5680         }
5681         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5682             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5683         }
5684
5685         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5686 }
5687
5688 int
5689 SetCharTable (char *table, const char * map)
5690 /* [HGM] moved here from winboard.c because of its general usefulness */
5691 /*       Basically a safe strcpy that uses the last character as King */
5692 {
5693     int result = FALSE; int NrPieces;
5694
5695     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5696                     && NrPieces >= 12 && !(NrPieces&1)) {
5697         int i; /* [HGM] Accept even length from 12 to 34 */
5698
5699         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5700         for( i=0; i<NrPieces/2-1; i++ ) {
5701             table[i] = map[i];
5702             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5703         }
5704         table[(int) WhiteKing]  = map[NrPieces/2-1];
5705         table[(int) BlackKing]  = map[NrPieces-1];
5706
5707         result = TRUE;
5708     }
5709
5710     return result;
5711 }
5712
5713 void
5714 Prelude (Board board)
5715 {       // [HGM] superchess: random selection of exo-pieces
5716         int i, j, k; ChessSquare p;
5717         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5718
5719         GetPositionNumber(); // use FRC position number
5720
5721         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5722             SetCharTable(pieceToChar, appData.pieceToCharTable);
5723             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5724                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5725         }
5726
5727         j = seed%4;                 seed /= 4;
5728         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5729         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5730         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5731         j = seed%3 + (seed%3 >= j); seed /= 3;
5732         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5733         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5734         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5735         j = seed%3;                 seed /= 3;
5736         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5737         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5738         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5739         j = seed%2 + (seed%2 >= j); seed /= 2;
5740         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5741         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5742         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5743         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5744         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5745         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5746         put(board, exoPieces[0],    0, 0, ANY);
5747         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5748 }
5749
5750 void
5751 InitPosition (int redraw)
5752 {
5753     ChessSquare (* pieces)[BOARD_FILES];
5754     int i, j, pawnRow, overrule,
5755     oldx = gameInfo.boardWidth,
5756     oldy = gameInfo.boardHeight,
5757     oldh = gameInfo.holdingsWidth;
5758     static int oldv;
5759
5760     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5761
5762     /* [AS] Initialize pv info list [HGM] and game status */
5763     {
5764         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5765             pvInfoList[i].depth = 0;
5766             boards[i][EP_STATUS] = EP_NONE;
5767             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5768         }
5769
5770         initialRulePlies = 0; /* 50-move counter start */
5771
5772         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5773         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5774     }
5775
5776
5777     /* [HGM] logic here is completely changed. In stead of full positions */
5778     /* the initialized data only consist of the two backranks. The switch */
5779     /* selects which one we will use, which is than copied to the Board   */
5780     /* initialPosition, which for the rest is initialized by Pawns and    */
5781     /* empty squares. This initial position is then copied to boards[0],  */
5782     /* possibly after shuffling, so that it remains available.            */
5783
5784     gameInfo.holdingsWidth = 0; /* default board sizes */
5785     gameInfo.boardWidth    = 8;
5786     gameInfo.boardHeight   = 8;
5787     gameInfo.holdingsSize  = 0;
5788     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5789     for(i=0; i<BOARD_FILES-2; i++)
5790       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5791     initialPosition[EP_STATUS] = EP_NONE;
5792     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5793     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5794          SetCharTable(pieceNickName, appData.pieceNickNames);
5795     else SetCharTable(pieceNickName, "............");
5796     pieces = FIDEArray;
5797
5798     switch (gameInfo.variant) {
5799     case VariantFischeRandom:
5800       shuffleOpenings = TRUE;
5801     default:
5802       break;
5803     case VariantShatranj:
5804       pieces = ShatranjArray;
5805       nrCastlingRights = 0;
5806       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5807       break;
5808     case VariantMakruk:
5809       pieces = makrukArray;
5810       nrCastlingRights = 0;
5811       startedFromSetupPosition = TRUE;
5812       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5813       break;
5814     case VariantTwoKings:
5815       pieces = twoKingsArray;
5816       break;
5817     case VariantGrand:
5818       pieces = GrandArray;
5819       nrCastlingRights = 0;
5820       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5821       gameInfo.boardWidth = 10;
5822       gameInfo.boardHeight = 10;
5823       gameInfo.holdingsSize = 7;
5824       break;
5825     case VariantCapaRandom:
5826       shuffleOpenings = TRUE;
5827     case VariantCapablanca:
5828       pieces = CapablancaArray;
5829       gameInfo.boardWidth = 10;
5830       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5831       break;
5832     case VariantGothic:
5833       pieces = GothicArray;
5834       gameInfo.boardWidth = 10;
5835       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5836       break;
5837     case VariantSChess:
5838       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5839       gameInfo.holdingsSize = 7;
5840       break;
5841     case VariantJanus:
5842       pieces = JanusArray;
5843       gameInfo.boardWidth = 10;
5844       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5845       nrCastlingRights = 6;
5846         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5847         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5848         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5849         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5850         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5851         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5852       break;
5853     case VariantFalcon:
5854       pieces = FalconArray;
5855       gameInfo.boardWidth = 10;
5856       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5857       break;
5858     case VariantXiangqi:
5859       pieces = XiangqiArray;
5860       gameInfo.boardWidth  = 9;
5861       gameInfo.boardHeight = 10;
5862       nrCastlingRights = 0;
5863       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5864       break;
5865     case VariantShogi:
5866       pieces = ShogiArray;
5867       gameInfo.boardWidth  = 9;
5868       gameInfo.boardHeight = 9;
5869       gameInfo.holdingsSize = 7;
5870       nrCastlingRights = 0;
5871       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5872       break;
5873     case VariantCourier:
5874       pieces = CourierArray;
5875       gameInfo.boardWidth  = 12;
5876       nrCastlingRights = 0;
5877       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5878       break;
5879     case VariantKnightmate:
5880       pieces = KnightmateArray;
5881       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5882       break;
5883     case VariantSpartan:
5884       pieces = SpartanArray;
5885       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5886       break;
5887     case VariantFairy:
5888       pieces = fairyArray;
5889       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5890       break;
5891     case VariantGreat:
5892       pieces = GreatArray;
5893       gameInfo.boardWidth = 10;
5894       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5895       gameInfo.holdingsSize = 8;
5896       break;
5897     case VariantSuper:
5898       pieces = FIDEArray;
5899       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5900       gameInfo.holdingsSize = 8;
5901       startedFromSetupPosition = TRUE;
5902       break;
5903     case VariantCrazyhouse:
5904     case VariantBughouse:
5905       pieces = FIDEArray;
5906       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5907       gameInfo.holdingsSize = 5;
5908       break;
5909     case VariantWildCastle:
5910       pieces = FIDEArray;
5911       /* !!?shuffle with kings guaranteed to be on d or e file */
5912       shuffleOpenings = 1;
5913       break;
5914     case VariantNoCastle:
5915       pieces = FIDEArray;
5916       nrCastlingRights = 0;
5917       /* !!?unconstrained back-rank shuffle */
5918       shuffleOpenings = 1;
5919       break;
5920     }
5921
5922     overrule = 0;
5923     if(appData.NrFiles >= 0) {
5924         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5925         gameInfo.boardWidth = appData.NrFiles;
5926     }
5927     if(appData.NrRanks >= 0) {
5928         gameInfo.boardHeight = appData.NrRanks;
5929     }
5930     if(appData.holdingsSize >= 0) {
5931         i = appData.holdingsSize;
5932         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5933         gameInfo.holdingsSize = i;
5934     }
5935     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5936     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5937         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5938
5939     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5940     if(pawnRow < 1) pawnRow = 1;
5941     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5942
5943     /* User pieceToChar list overrules defaults */
5944     if(appData.pieceToCharTable != NULL)
5945         SetCharTable(pieceToChar, appData.pieceToCharTable);
5946
5947     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5948
5949         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5950             s = (ChessSquare) 0; /* account holding counts in guard band */
5951         for( i=0; i<BOARD_HEIGHT; i++ )
5952             initialPosition[i][j] = s;
5953
5954         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5955         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5956         initialPosition[pawnRow][j] = WhitePawn;
5957         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5958         if(gameInfo.variant == VariantXiangqi) {
5959             if(j&1) {
5960                 initialPosition[pawnRow][j] =
5961                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5962                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5963                    initialPosition[2][j] = WhiteCannon;
5964                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5965                 }
5966             }
5967         }
5968         if(gameInfo.variant == VariantGrand) {
5969             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5970                initialPosition[0][j] = WhiteRook;
5971                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5972             }
5973         }
5974         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5975     }
5976     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5977
5978             j=BOARD_LEFT+1;
5979             initialPosition[1][j] = WhiteBishop;
5980             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5981             j=BOARD_RGHT-2;
5982             initialPosition[1][j] = WhiteRook;
5983             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5984     }
5985
5986     if( nrCastlingRights == -1) {
5987         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5988         /*       This sets default castling rights from none to normal corners   */
5989         /* Variants with other castling rights must set them themselves above    */
5990         nrCastlingRights = 6;
5991
5992         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5993         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5994         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5995         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5996         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5997         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5998      }
5999
6000      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6001      if(gameInfo.variant == VariantGreat) { // promotion commoners
6002         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6003         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6004         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6005         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6006      }
6007      if( gameInfo.variant == VariantSChess ) {
6008       initialPosition[1][0] = BlackMarshall;
6009       initialPosition[2][0] = BlackAngel;
6010       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6011       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6012       initialPosition[1][1] = initialPosition[2][1] = 
6013       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6014      }
6015   if (appData.debugMode) {
6016     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6017   }
6018     if(shuffleOpenings) {
6019         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6020         startedFromSetupPosition = TRUE;
6021     }
6022     if(startedFromPositionFile) {
6023       /* [HGM] loadPos: use PositionFile for every new game */
6024       CopyBoard(initialPosition, filePosition);
6025       for(i=0; i<nrCastlingRights; i++)
6026           initialRights[i] = filePosition[CASTLING][i];
6027       startedFromSetupPosition = TRUE;
6028     }
6029
6030     CopyBoard(boards[0], initialPosition);
6031
6032     if(oldx != gameInfo.boardWidth ||
6033        oldy != gameInfo.boardHeight ||
6034        oldv != gameInfo.variant ||
6035        oldh != gameInfo.holdingsWidth
6036                                          )
6037             InitDrawingSizes(-2 ,0);
6038
6039     oldv = gameInfo.variant;
6040     if (redraw)
6041       DrawPosition(TRUE, boards[currentMove]);
6042 }
6043
6044 void
6045 SendBoard (ChessProgramState *cps, int moveNum)
6046 {
6047     char message[MSG_SIZ];
6048
6049     if (cps->useSetboard) {
6050       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6051       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6052       SendToProgram(message, cps);
6053       free(fen);
6054
6055     } else {
6056       ChessSquare *bp;
6057       int i, j, left=0, right=BOARD_WIDTH;
6058       /* Kludge to set black to move, avoiding the troublesome and now
6059        * deprecated "black" command.
6060        */
6061       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6062         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6063
6064       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6065
6066       SendToProgram("edit\n", cps);
6067       SendToProgram("#\n", cps);
6068       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6069         bp = &boards[moveNum][i][left];
6070         for (j = left; j < right; j++, bp++) {
6071           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6072           if ((int) *bp < (int) BlackPawn) {
6073             if(j == BOARD_RGHT+1)
6074                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6075             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6076             if(message[0] == '+' || message[0] == '~') {
6077               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6078                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6079                         AAA + j, ONE + i);
6080             }
6081             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6082                 message[1] = BOARD_RGHT   - 1 - j + '1';
6083                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6084             }
6085             SendToProgram(message, cps);
6086           }
6087         }
6088       }
6089
6090       SendToProgram("c\n", cps);
6091       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6092         bp = &boards[moveNum][i][left];
6093         for (j = left; j < right; j++, bp++) {
6094           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6095           if (((int) *bp != (int) EmptySquare)
6096               && ((int) *bp >= (int) BlackPawn)) {
6097             if(j == BOARD_LEFT-2)
6098                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6099             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6100                     AAA + j, ONE + i);
6101             if(message[0] == '+' || message[0] == '~') {
6102               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6103                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6104                         AAA + j, ONE + i);
6105             }
6106             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6107                 message[1] = BOARD_RGHT   - 1 - j + '1';
6108                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6109             }
6110             SendToProgram(message, cps);
6111           }
6112         }
6113       }
6114
6115       SendToProgram(".\n", cps);
6116     }
6117     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6118 }
6119
6120 char exclusionHeader[MSG_SIZ];
6121 int exCnt, excludePtr;
6122 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6123 static Exclusion excluTab[200];
6124 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6125
6126 static void
6127 WriteMap (int s)
6128 {
6129     int j;
6130     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6131     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6132 }
6133
6134 static void
6135 ClearMap ()
6136 {
6137     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6138     excludePtr = 24; exCnt = 0;
6139     WriteMap(0);
6140 }
6141
6142 static void
6143 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6144 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6145     char buf[2*MOVE_LEN], *p;
6146     Exclusion *e = excluTab;
6147     int i;
6148     for(i=0; i<exCnt; i++)
6149         if(e[i].ff == fromX && e[i].fr == fromY &&
6150            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6151     if(i == exCnt) { // was not in exclude list; add it
6152         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6153         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6154             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6155             return; // abort
6156         }
6157         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6158         excludePtr++; e[i].mark = excludePtr++;
6159         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6160         exCnt++;
6161     }
6162     exclusionHeader[e[i].mark] = state;
6163 }
6164
6165 static int
6166 ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, char state)
6167 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6168     char buf[MSG_SIZ];
6169     int j, k;
6170     ChessMove moveType;
6171     if(promoChar == -1) { // kludge to indicate best move
6172         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6173             return 1; // if unparsable, abort
6174     }
6175     // update exclusion map (resolving toggle by consulting existing state)
6176     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6177     j = k%8; k >>= 3;
6178     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6179     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6180          excludeMap[k] |=   1<<j;
6181     else excludeMap[k] &= ~(1<<j);
6182     // update header
6183     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6184     // inform engine
6185     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6186     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6187     SendToProgram(buf, &first);
6188     return (state == '+');
6189 }
6190
6191 static void
6192 ExcludeClick (int index)
6193 {
6194     int i, j;
6195     Exclusion *e = excluTab;
6196     if(index < 25) { // none, best or tail clicked
6197         if(index < 13) { // none: include all
6198             WriteMap(0); // clear map
6199             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6200             SendToProgram("include all\n", &first); // and inform engine
6201         } else if(index > 18) { // tail
6202             if(exclusionHeader[19] == '-') { // tail was excluded
6203                 SendToProgram("include all\n", &first);
6204                 WriteMap(0); // clear map completely
6205                 // now re-exclude selected moves
6206                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6207                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6208             } else { // tail was included or in mixed state
6209                 SendToProgram("exclude all\n", &first);
6210                 WriteMap(0xFF); // fill map completely
6211                 // now re-include selected moves
6212                 j = 0; // count them
6213                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6214                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6215                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6216             }
6217         } else { // best
6218             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6219         }
6220     } else {
6221         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6222             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6223             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6224             break;
6225         }
6226     }
6227 }
6228
6229 ChessSquare
6230 DefaultPromoChoice (int white)
6231 {
6232     ChessSquare result;
6233     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6234         result = WhiteFerz; // no choice
6235     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6236         result= WhiteKing; // in Suicide Q is the last thing we want
6237     else if(gameInfo.variant == VariantSpartan)
6238         result = white ? WhiteQueen : WhiteAngel;
6239     else result = WhiteQueen;
6240     if(!white) result = WHITE_TO_BLACK result;
6241     return result;
6242 }
6243
6244 static int autoQueen; // [HGM] oneclick
6245
6246 int
6247 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6248 {
6249     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6250     /* [HGM] add Shogi promotions */
6251     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6252     ChessSquare piece;
6253     ChessMove moveType;
6254     Boolean premove;
6255
6256     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6257     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6258
6259     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6260       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6261         return FALSE;
6262
6263     piece = boards[currentMove][fromY][fromX];
6264     if(gameInfo.variant == VariantShogi) {
6265         promotionZoneSize = BOARD_HEIGHT/3;
6266         highestPromotingPiece = (int)WhiteFerz;
6267     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6268         promotionZoneSize = 3;
6269     }
6270
6271     // Treat Lance as Pawn when it is not representing Amazon
6272     if(gameInfo.variant != VariantSuper) {
6273         if(piece == WhiteLance) piece = WhitePawn; else
6274         if(piece == BlackLance) piece = BlackPawn;
6275     }
6276
6277     // next weed out all moves that do not touch the promotion zone at all
6278     if((int)piece >= BlackPawn) {
6279         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6280              return FALSE;
6281         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6282     } else {
6283         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6284            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6285     }
6286
6287     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6288
6289     // weed out mandatory Shogi promotions
6290     if(gameInfo.variant == VariantShogi) {
6291         if(piece >= BlackPawn) {
6292             if(toY == 0 && piece == BlackPawn ||
6293                toY == 0 && piece == BlackQueen ||
6294                toY <= 1 && piece == BlackKnight) {
6295                 *promoChoice = '+';
6296                 return FALSE;
6297             }
6298         } else {
6299             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6300                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6301                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6302                 *promoChoice = '+';
6303                 return FALSE;
6304             }
6305         }
6306     }
6307
6308     // weed out obviously illegal Pawn moves
6309     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6310         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6311         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6312         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6313         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6314         // note we are not allowed to test for valid (non-)capture, due to premove
6315     }
6316
6317     // we either have a choice what to promote to, or (in Shogi) whether to promote
6318     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6319         *promoChoice = PieceToChar(BlackFerz);  // no choice
6320         return FALSE;
6321     }
6322     // no sense asking what we must promote to if it is going to explode...
6323     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6324         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6325         return FALSE;
6326     }
6327     // give caller the default choice even if we will not make it
6328     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6329     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6330     if(        sweepSelect && gameInfo.variant != VariantGreat
6331                            && gameInfo.variant != VariantGrand
6332                            && gameInfo.variant != VariantSuper) return FALSE;
6333     if(autoQueen) return FALSE; // predetermined
6334
6335     // suppress promotion popup on illegal moves that are not premoves
6336     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6337               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6338     if(appData.testLegality && !premove) {
6339         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6340                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6341         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6342             return FALSE;
6343     }
6344
6345     return TRUE;
6346 }
6347
6348 int
6349 InPalace (int row, int column)
6350 {   /* [HGM] for Xiangqi */
6351     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6352          column < (BOARD_WIDTH + 4)/2 &&
6353          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6354     return FALSE;
6355 }
6356
6357 int
6358 PieceForSquare (int x, int y)
6359 {
6360   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6361      return -1;
6362   else
6363      return boards[currentMove][y][x];
6364 }
6365
6366 int
6367 OKToStartUserMove (int x, int y)
6368 {
6369     ChessSquare from_piece;
6370     int white_piece;
6371
6372     if (matchMode) return FALSE;
6373     if (gameMode == EditPosition) return TRUE;
6374
6375     if (x >= 0 && y >= 0)
6376       from_piece = boards[currentMove][y][x];
6377     else
6378       from_piece = EmptySquare;
6379
6380     if (from_piece == EmptySquare) return FALSE;
6381
6382     white_piece = (int)from_piece >= (int)WhitePawn &&
6383       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6384
6385     switch (gameMode) {
6386       case AnalyzeFile:
6387       case TwoMachinesPlay:
6388       case EndOfGame:
6389         return FALSE;
6390
6391       case IcsObserving:
6392       case IcsIdle:
6393         return FALSE;
6394
6395       case MachinePlaysWhite:
6396       case IcsPlayingBlack:
6397         if (appData.zippyPlay) return FALSE;
6398         if (white_piece) {
6399             DisplayMoveError(_("You are playing Black"));
6400             return FALSE;
6401         }
6402         break;
6403
6404       case MachinePlaysBlack:
6405       case IcsPlayingWhite:
6406         if (appData.zippyPlay) return FALSE;
6407         if (!white_piece) {
6408             DisplayMoveError(_("You are playing White"));
6409             return FALSE;
6410         }
6411         break;
6412
6413       case PlayFromGameFile:
6414             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6415       case EditGame:
6416         if (!white_piece && WhiteOnMove(currentMove)) {
6417             DisplayMoveError(_("It is White's turn"));
6418             return FALSE;
6419         }
6420         if (white_piece && !WhiteOnMove(currentMove)) {
6421             DisplayMoveError(_("It is Black's turn"));
6422             return FALSE;
6423         }
6424         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6425             /* Editing correspondence game history */
6426             /* Could disallow this or prompt for confirmation */
6427             cmailOldMove = -1;
6428         }
6429         break;
6430
6431       case BeginningOfGame:
6432         if (appData.icsActive) return FALSE;
6433         if (!appData.noChessProgram) {
6434             if (!white_piece) {
6435                 DisplayMoveError(_("You are playing White"));
6436                 return FALSE;
6437             }
6438         }
6439         break;
6440
6441       case Training:
6442         if (!white_piece && WhiteOnMove(currentMove)) {
6443             DisplayMoveError(_("It is White's turn"));
6444             return FALSE;
6445         }
6446         if (white_piece && !WhiteOnMove(currentMove)) {
6447             DisplayMoveError(_("It is Black's turn"));
6448             return FALSE;
6449         }
6450         break;
6451
6452       default:
6453       case IcsExamining:
6454         break;
6455     }
6456     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6457         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6458         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6459         && gameMode != AnalyzeFile && gameMode != Training) {
6460         DisplayMoveError(_("Displayed position is not current"));
6461         return FALSE;
6462     }
6463     return TRUE;
6464 }
6465
6466 Boolean
6467 OnlyMove (int *x, int *y, Boolean captures) 
6468 {
6469     DisambiguateClosure cl;
6470     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6471     switch(gameMode) {
6472       case MachinePlaysBlack:
6473       case IcsPlayingWhite:
6474       case BeginningOfGame:
6475         if(!WhiteOnMove(currentMove)) return FALSE;
6476         break;
6477       case MachinePlaysWhite:
6478       case IcsPlayingBlack:
6479         if(WhiteOnMove(currentMove)) return FALSE;
6480         break;
6481       case EditGame:
6482         break;
6483       default:
6484         return FALSE;
6485     }
6486     cl.pieceIn = EmptySquare;
6487     cl.rfIn = *y;
6488     cl.ffIn = *x;
6489     cl.rtIn = -1;
6490     cl.ftIn = -1;
6491     cl.promoCharIn = NULLCHAR;
6492     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6493     if( cl.kind == NormalMove ||
6494         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6495         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6496         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6497       fromX = cl.ff;
6498       fromY = cl.rf;
6499       *x = cl.ft;
6500       *y = cl.rt;
6501       return TRUE;
6502     }
6503     if(cl.kind != ImpossibleMove) return FALSE;
6504     cl.pieceIn = EmptySquare;
6505     cl.rfIn = -1;
6506     cl.ffIn = -1;
6507     cl.rtIn = *y;
6508     cl.ftIn = *x;
6509     cl.promoCharIn = NULLCHAR;
6510     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6511     if( cl.kind == NormalMove ||
6512         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6513         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6514         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6515       fromX = cl.ff;
6516       fromY = cl.rf;
6517       *x = cl.ft;
6518       *y = cl.rt;
6519       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6520       return TRUE;
6521     }
6522     return FALSE;
6523 }
6524
6525 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6526 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6527 int lastLoadGameUseList = FALSE;
6528 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6529 ChessMove lastLoadGameStart = EndOfFile;
6530 int doubleClick;
6531
6532 void
6533 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6534 {
6535     ChessMove moveType;
6536     ChessSquare pdown, pup;
6537     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6538
6539
6540     /* Check if the user is playing in turn.  This is complicated because we
6541        let the user "pick up" a piece before it is his turn.  So the piece he
6542        tried to pick up may have been captured by the time he puts it down!
6543        Therefore we use the color the user is supposed to be playing in this
6544        test, not the color of the piece that is currently on the starting
6545        square---except in EditGame mode, where the user is playing both
6546        sides; fortunately there the capture race can't happen.  (It can
6547        now happen in IcsExamining mode, but that's just too bad.  The user
6548        will get a somewhat confusing message in that case.)
6549        */
6550
6551     switch (gameMode) {
6552       case AnalyzeFile:
6553       case TwoMachinesPlay:
6554       case EndOfGame:
6555       case IcsObserving:
6556       case IcsIdle:
6557         /* We switched into a game mode where moves are not accepted,
6558            perhaps while the mouse button was down. */
6559         return;
6560
6561       case MachinePlaysWhite:
6562         /* User is moving for Black */
6563         if (WhiteOnMove(currentMove)) {
6564             DisplayMoveError(_("It is White's turn"));
6565             return;
6566         }
6567         break;
6568
6569       case MachinePlaysBlack:
6570         /* User is moving for White */
6571         if (!WhiteOnMove(currentMove)) {
6572             DisplayMoveError(_("It is Black's turn"));
6573             return;
6574         }
6575         break;
6576
6577       case PlayFromGameFile:
6578             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6579       case EditGame:
6580       case IcsExamining:
6581       case BeginningOfGame:
6582       case AnalyzeMode:
6583       case Training:
6584         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6585         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6586             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6587             /* User is moving for Black */
6588             if (WhiteOnMove(currentMove)) {
6589                 DisplayMoveError(_("It is White's turn"));
6590                 return;
6591             }
6592         } else {
6593             /* User is moving for White */
6594             if (!WhiteOnMove(currentMove)) {
6595                 DisplayMoveError(_("It is Black's turn"));
6596                 return;
6597             }
6598         }
6599         break;
6600
6601       case IcsPlayingBlack:
6602         /* User is moving for Black */
6603         if (WhiteOnMove(currentMove)) {
6604             if (!appData.premove) {
6605                 DisplayMoveError(_("It is White's turn"));
6606             } else if (toX >= 0 && toY >= 0) {
6607                 premoveToX = toX;
6608                 premoveToY = toY;
6609                 premoveFromX = fromX;
6610                 premoveFromY = fromY;
6611                 premovePromoChar = promoChar;
6612                 gotPremove = 1;
6613                 if (appData.debugMode)
6614                     fprintf(debugFP, "Got premove: fromX %d,"
6615                             "fromY %d, toX %d, toY %d\n",
6616                             fromX, fromY, toX, toY);
6617             }
6618             return;
6619         }
6620         break;
6621
6622       case IcsPlayingWhite:
6623         /* User is moving for White */
6624         if (!WhiteOnMove(currentMove)) {
6625             if (!appData.premove) {
6626                 DisplayMoveError(_("It is Black's turn"));
6627             } else if (toX >= 0 && toY >= 0) {
6628                 premoveToX = toX;
6629                 premoveToY = toY;
6630                 premoveFromX = fromX;
6631                 premoveFromY = fromY;
6632                 premovePromoChar = promoChar;
6633                 gotPremove = 1;
6634                 if (appData.debugMode)
6635                     fprintf(debugFP, "Got premove: fromX %d,"
6636                             "fromY %d, toX %d, toY %d\n",
6637                             fromX, fromY, toX, toY);
6638             }
6639             return;
6640         }
6641         break;
6642
6643       default:
6644         break;
6645
6646       case EditPosition:
6647         /* EditPosition, empty square, or different color piece;
6648            click-click move is possible */
6649         if (toX == -2 || toY == -2) {
6650             boards[0][fromY][fromX] = EmptySquare;
6651             DrawPosition(FALSE, boards[currentMove]);
6652             return;
6653         } else if (toX >= 0 && toY >= 0) {
6654             boards[0][toY][toX] = boards[0][fromY][fromX];
6655             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6656                 if(boards[0][fromY][0] != EmptySquare) {
6657                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6658                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6659                 }
6660             } else
6661             if(fromX == BOARD_RGHT+1) {
6662                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6663                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6664                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6665                 }
6666             } else
6667             boards[0][fromY][fromX] = gatingPiece;
6668             DrawPosition(FALSE, boards[currentMove]);
6669             return;
6670         }
6671         return;
6672     }
6673
6674     if(toX < 0 || toY < 0) return;
6675     pdown = boards[currentMove][fromY][fromX];
6676     pup = boards[currentMove][toY][toX];
6677
6678     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6679     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6680          if( pup != EmptySquare ) return;
6681          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6682            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6683                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6684            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6685            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6686            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6687            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6688          fromY = DROP_RANK;
6689     }
6690
6691     /* [HGM] always test for legality, to get promotion info */
6692     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6693                                          fromY, fromX, toY, toX, promoChar);
6694
6695     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6696
6697     /* [HGM] but possibly ignore an IllegalMove result */
6698     if (appData.testLegality) {
6699         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6700             DisplayMoveError(_("Illegal move"));
6701             return;
6702         }
6703     }
6704
6705     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6706         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6707              ClearPremoveHighlights(); // was included
6708         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6709         return;
6710     }
6711
6712     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6713 }
6714
6715 /* Common tail of UserMoveEvent and DropMenuEvent */
6716 int
6717 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6718 {
6719     char *bookHit = 0;
6720
6721     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6722         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6723         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6724         if(WhiteOnMove(currentMove)) {
6725             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6726         } else {
6727             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6728         }
6729     }
6730
6731     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6732        move type in caller when we know the move is a legal promotion */
6733     if(moveType == NormalMove && promoChar)
6734         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6735
6736     /* [HGM] <popupFix> The following if has been moved here from
6737        UserMoveEvent(). Because it seemed to belong here (why not allow
6738        piece drops in training games?), and because it can only be
6739        performed after it is known to what we promote. */
6740     if (gameMode == Training) {
6741       /* compare the move played on the board to the next move in the
6742        * game. If they match, display the move and the opponent's response.
6743        * If they don't match, display an error message.
6744        */
6745       int saveAnimate;
6746       Board testBoard;
6747       CopyBoard(testBoard, boards[currentMove]);
6748       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6749
6750       if (CompareBoards(testBoard, boards[currentMove+1])) {
6751         ForwardInner(currentMove+1);
6752
6753         /* Autoplay the opponent's response.
6754          * if appData.animate was TRUE when Training mode was entered,
6755          * the response will be animated.
6756          */
6757         saveAnimate = appData.animate;
6758         appData.animate = animateTraining;
6759         ForwardInner(currentMove+1);
6760         appData.animate = saveAnimate;
6761
6762         /* check for the end of the game */
6763         if (currentMove >= forwardMostMove) {
6764           gameMode = PlayFromGameFile;
6765           ModeHighlight();
6766           SetTrainingModeOff();
6767           DisplayInformation(_("End of game"));
6768         }
6769       } else {
6770         DisplayError(_("Incorrect move"), 0);
6771       }
6772       return 1;
6773     }
6774
6775   /* Ok, now we know that the move is good, so we can kill
6776      the previous line in Analysis Mode */
6777   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6778                                 && currentMove < forwardMostMove) {
6779     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6780     else forwardMostMove = currentMove;
6781   }
6782
6783   ClearMap();
6784
6785   /* If we need the chess program but it's dead, restart it */
6786   ResurrectChessProgram();
6787
6788   /* A user move restarts a paused game*/
6789   if (pausing)
6790     PauseEvent();
6791
6792   thinkOutput[0] = NULLCHAR;
6793
6794   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6795
6796   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6797     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6798     return 1;
6799   }
6800
6801   if (gameMode == BeginningOfGame) {
6802     if (appData.noChessProgram) {
6803       gameMode = EditGame;
6804       SetGameInfo();
6805     } else {
6806       char buf[MSG_SIZ];
6807       gameMode = MachinePlaysBlack;
6808       StartClocks();
6809       SetGameInfo();
6810       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6811       DisplayTitle(buf);
6812       if (first.sendName) {
6813         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6814         SendToProgram(buf, &first);
6815       }
6816       StartClocks();
6817     }
6818     ModeHighlight();
6819   }
6820
6821   /* Relay move to ICS or chess engine */
6822   if (appData.icsActive) {
6823     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6824         gameMode == IcsExamining) {
6825       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6826         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6827         SendToICS("draw ");
6828         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6829       }
6830       // also send plain move, in case ICS does not understand atomic claims
6831       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6832       ics_user_moved = 1;
6833     }
6834   } else {
6835     if (first.sendTime && (gameMode == BeginningOfGame ||
6836                            gameMode == MachinePlaysWhite ||
6837                            gameMode == MachinePlaysBlack)) {
6838       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6839     }
6840     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6841          // [HGM] book: if program might be playing, let it use book
6842         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6843         first.maybeThinking = TRUE;
6844     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6845         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6846         SendBoard(&first, currentMove+1);
6847     } else SendMoveToProgram(forwardMostMove-1, &first);
6848     if (currentMove == cmailOldMove + 1) {
6849       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6850     }
6851   }
6852
6853   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6854
6855   switch (gameMode) {
6856   case EditGame:
6857     if(appData.testLegality)
6858     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6859     case MT_NONE:
6860     case MT_CHECK:
6861       break;
6862     case MT_CHECKMATE:
6863     case MT_STAINMATE:
6864       if (WhiteOnMove(currentMove)) {
6865         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6866       } else {
6867         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6868       }
6869       break;
6870     case MT_STALEMATE:
6871       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6872       break;
6873     }
6874     break;
6875
6876   case MachinePlaysBlack:
6877   case MachinePlaysWhite:
6878     /* disable certain menu options while machine is thinking */
6879     SetMachineThinkingEnables();
6880     break;
6881
6882   default:
6883     break;
6884   }
6885
6886   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6887   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6888
6889   if(bookHit) { // [HGM] book: simulate book reply
6890         static char bookMove[MSG_SIZ]; // a bit generous?
6891
6892         programStats.nodes = programStats.depth = programStats.time =
6893         programStats.score = programStats.got_only_move = 0;
6894         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6895
6896         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6897         strcat(bookMove, bookHit);
6898         HandleMachineMove(bookMove, &first);
6899   }
6900   return 1;
6901 }
6902
6903 void
6904 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6905 {
6906     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6907     Markers *m = (Markers *) closure;
6908     if(rf == fromY && ff == fromX)
6909         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6910                          || kind == WhiteCapturesEnPassant
6911                          || kind == BlackCapturesEnPassant);
6912     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6913 }
6914
6915 void
6916 MarkTargetSquares (int clear)
6917 {
6918   int x, y;
6919   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6920      !appData.testLegality || gameMode == EditPosition) return;
6921   if(clear) {
6922     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6923   } else {
6924     int capt = 0;
6925     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6926     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6927       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6928       if(capt)
6929       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6930     }
6931   }
6932   DrawPosition(TRUE, NULL);
6933 }
6934
6935 int
6936 Explode (Board board, int fromX, int fromY, int toX, int toY)
6937 {
6938     if(gameInfo.variant == VariantAtomic &&
6939        (board[toY][toX] != EmptySquare ||                     // capture?
6940         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6941                          board[fromY][fromX] == BlackPawn   )
6942       )) {
6943         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6944         return TRUE;
6945     }
6946     return FALSE;
6947 }
6948
6949 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6950
6951 int
6952 CanPromote (ChessSquare piece, int y)
6953 {
6954         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6955         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6956         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6957            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6958            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6959                                                   gameInfo.variant == VariantMakruk) return FALSE;
6960         return (piece == BlackPawn && y == 1 ||
6961                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6962                 piece == BlackLance && y == 1 ||
6963                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6964 }
6965
6966 void
6967 LeftClick (ClickType clickType, int xPix, int yPix)
6968 {
6969     int x, y;
6970     Boolean saveAnimate;
6971     static int second = 0, promotionChoice = 0, clearFlag = 0;
6972     char promoChoice = NULLCHAR;
6973     ChessSquare piece;
6974     static TimeMark lastClickTime, prevClickTime;
6975
6976     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
6977
6978     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
6979
6980     if (clickType == Press) ErrorPopDown();
6981
6982     x = EventToSquare(xPix, BOARD_WIDTH);
6983     y = EventToSquare(yPix, BOARD_HEIGHT);
6984     if (!flipView && y >= 0) {
6985         y = BOARD_HEIGHT - 1 - y;
6986     }
6987     if (flipView && x >= 0) {
6988         x = BOARD_WIDTH - 1 - x;
6989     }
6990
6991     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6992         defaultPromoChoice = promoSweep;
6993         promoSweep = EmptySquare;   // terminate sweep
6994         promoDefaultAltered = TRUE;
6995         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6996     }
6997
6998     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6999         if(clickType == Release) return; // ignore upclick of click-click destination
7000         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7001         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7002         if(gameInfo.holdingsWidth &&
7003                 (WhiteOnMove(currentMove)
7004                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7005                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7006             // click in right holdings, for determining promotion piece
7007             ChessSquare p = boards[currentMove][y][x];
7008             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7009             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7010             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7011                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7012                 fromX = fromY = -1;
7013                 return;
7014             }
7015         }
7016         DrawPosition(FALSE, boards[currentMove]);
7017         return;
7018     }
7019
7020     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7021     if(clickType == Press
7022             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7023               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7024               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7025         return;
7026
7027     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7028         // could be static click on premove from-square: abort premove
7029         gotPremove = 0;
7030         ClearPremoveHighlights();
7031     }
7032
7033     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7034         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7035
7036     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7037         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7038                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7039         defaultPromoChoice = DefaultPromoChoice(side);
7040     }
7041
7042     autoQueen = appData.alwaysPromoteToQueen;
7043
7044     if (fromX == -1) {
7045       int originalY = y;
7046       gatingPiece = EmptySquare;
7047       if (clickType != Press) {
7048         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7049             DragPieceEnd(xPix, yPix); dragging = 0;
7050             DrawPosition(FALSE, NULL);
7051         }
7052         return;
7053       }
7054       doubleClick = FALSE;
7055       fromX = x; fromY = y; toX = toY = -1;
7056       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7057          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7058          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7059             /* First square */
7060             if (OKToStartUserMove(fromX, fromY)) {
7061                 second = 0;
7062                 MarkTargetSquares(0);
7063                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7064                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7065                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7066                     promoSweep = defaultPromoChoice;
7067                     selectFlag = 0; lastX = xPix; lastY = yPix;
7068                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7069                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7070                 }
7071                 if (appData.highlightDragging) {
7072                     SetHighlights(fromX, fromY, -1, -1);
7073                 }
7074             } else fromX = fromY = -1;
7075             return;
7076         }
7077     }
7078
7079     /* fromX != -1 */
7080     if (clickType == Press && gameMode != EditPosition) {
7081         ChessSquare fromP;
7082         ChessSquare toP;
7083         int frc;
7084
7085         // ignore off-board to clicks
7086         if(y < 0 || x < 0) return;
7087
7088         /* Check if clicking again on the same color piece */
7089         fromP = boards[currentMove][fromY][fromX];
7090         toP = boards[currentMove][y][x];
7091         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7092         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7093              WhitePawn <= toP && toP <= WhiteKing &&
7094              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7095              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7096             (BlackPawn <= fromP && fromP <= BlackKing &&
7097              BlackPawn <= toP && toP <= BlackKing &&
7098              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7099              !(fromP == BlackKing && toP == BlackRook && frc))) {
7100             /* Clicked again on same color piece -- changed his mind */
7101             second = (x == fromX && y == fromY);
7102             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7103                 second = FALSE; // first double-click rather than scond click
7104                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7105             }
7106             promoDefaultAltered = FALSE;
7107             MarkTargetSquares(1);
7108            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7109             if (appData.highlightDragging) {
7110                 SetHighlights(x, y, -1, -1);
7111             } else {
7112                 ClearHighlights();
7113             }
7114             if (OKToStartUserMove(x, y)) {
7115                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7116                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7117                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7118                  gatingPiece = boards[currentMove][fromY][fromX];
7119                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7120                 fromX = x;
7121                 fromY = y; dragging = 1;
7122                 MarkTargetSquares(0);
7123                 DragPieceBegin(xPix, yPix, FALSE);
7124                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7125                     promoSweep = defaultPromoChoice;
7126                     selectFlag = 0; lastX = xPix; lastY = yPix;
7127                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7128                 }
7129             }
7130            }
7131            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7132            second = FALSE; 
7133         }
7134         // ignore clicks on holdings
7135         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7136     }
7137
7138     if (clickType == Release && x == fromX && y == fromY) {
7139         DragPieceEnd(xPix, yPix); dragging = 0;
7140         if(clearFlag) {
7141             // a deferred attempt to click-click move an empty square on top of a piece
7142             boards[currentMove][y][x] = EmptySquare;
7143             ClearHighlights();
7144             DrawPosition(FALSE, boards[currentMove]);
7145             fromX = fromY = -1; clearFlag = 0;
7146             return;
7147         }
7148         if (appData.animateDragging) {
7149             /* Undo animation damage if any */
7150             DrawPosition(FALSE, NULL);
7151         }
7152         if (second) {
7153             /* Second up/down in same square; just abort move */
7154             second = 0;
7155             fromX = fromY = -1;
7156             gatingPiece = EmptySquare;
7157             ClearHighlights();
7158             gotPremove = 0;
7159             ClearPremoveHighlights();
7160         } else {
7161             /* First upclick in same square; start click-click mode */
7162             SetHighlights(x, y, -1, -1);
7163         }
7164         return;
7165     }
7166
7167     clearFlag = 0;
7168
7169     /* we now have a different from- and (possibly off-board) to-square */
7170     /* Completed move */
7171     toX = x;
7172     toY = y;
7173     saveAnimate = appData.animate;
7174     MarkTargetSquares(1);
7175     if (clickType == Press) {
7176         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7177             // must be Edit Position mode with empty-square selected
7178             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7179             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7180             return;
7181         }
7182         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7183             ChessSquare piece = boards[currentMove][fromY][fromX];
7184             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7185             promoSweep = defaultPromoChoice;
7186             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7187             selectFlag = 0; lastX = xPix; lastY = yPix;
7188             Sweep(0); // Pawn that is going to promote: preview promotion piece
7189             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7190             DrawPosition(FALSE, boards[currentMove]);
7191             return;
7192         }
7193         /* Finish clickclick move */
7194         if (appData.animate || appData.highlightLastMove) {
7195             SetHighlights(fromX, fromY, toX, toY);
7196         } else {
7197             ClearHighlights();
7198         }
7199     } else {
7200         /* Finish drag move */
7201         if (appData.highlightLastMove) {
7202             SetHighlights(fromX, fromY, toX, toY);
7203         } else {
7204             ClearHighlights();
7205         }
7206         DragPieceEnd(xPix, yPix); dragging = 0;
7207         /* Don't animate move and drag both */
7208         appData.animate = FALSE;
7209     }
7210
7211     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7212     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7213         ChessSquare piece = boards[currentMove][fromY][fromX];
7214         if(gameMode == EditPosition && piece != EmptySquare &&
7215            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7216             int n;
7217
7218             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7219                 n = PieceToNumber(piece - (int)BlackPawn);
7220                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7221                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7222                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7223             } else
7224             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7225                 n = PieceToNumber(piece);
7226                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7227                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7228                 boards[currentMove][n][BOARD_WIDTH-2]++;
7229             }
7230             boards[currentMove][fromY][fromX] = EmptySquare;
7231         }
7232         ClearHighlights();
7233         fromX = fromY = -1;
7234         DrawPosition(TRUE, boards[currentMove]);
7235         return;
7236     }
7237
7238     // off-board moves should not be highlighted
7239     if(x < 0 || y < 0) ClearHighlights();
7240
7241     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7242
7243     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7244         SetHighlights(fromX, fromY, toX, toY);
7245         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7246             // [HGM] super: promotion to captured piece selected from holdings
7247             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7248             promotionChoice = TRUE;
7249             // kludge follows to temporarily execute move on display, without promoting yet
7250             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7251             boards[currentMove][toY][toX] = p;
7252             DrawPosition(FALSE, boards[currentMove]);
7253             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7254             boards[currentMove][toY][toX] = q;
7255             DisplayMessage("Click in holdings to choose piece", "");
7256             return;
7257         }
7258         PromotionPopUp();
7259     } else {
7260         int oldMove = currentMove;
7261         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7262         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7263         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7264         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7265            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7266             DrawPosition(TRUE, boards[currentMove]);
7267         fromX = fromY = -1;
7268     }
7269     appData.animate = saveAnimate;
7270     if (appData.animate || appData.animateDragging) {
7271         /* Undo animation damage if needed */
7272         DrawPosition(FALSE, NULL);
7273     }
7274 }
7275
7276 int
7277 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7278 {   // front-end-free part taken out of PieceMenuPopup
7279     int whichMenu; int xSqr, ySqr;
7280
7281     if(seekGraphUp) { // [HGM] seekgraph
7282         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7283         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7284         return -2;
7285     }
7286
7287     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7288          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7289         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7290         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7291         if(action == Press)   {
7292             originalFlip = flipView;
7293             flipView = !flipView; // temporarily flip board to see game from partners perspective
7294             DrawPosition(TRUE, partnerBoard);
7295             DisplayMessage(partnerStatus, "");
7296             partnerUp = TRUE;
7297         } else if(action == Release) {
7298             flipView = originalFlip;
7299             DrawPosition(TRUE, boards[currentMove]);
7300             partnerUp = FALSE;
7301         }
7302         return -2;
7303     }
7304
7305     xSqr = EventToSquare(x, BOARD_WIDTH);
7306     ySqr = EventToSquare(y, BOARD_HEIGHT);
7307     if (action == Release) {
7308         if(pieceSweep != EmptySquare) {
7309             EditPositionMenuEvent(pieceSweep, toX, toY);
7310             pieceSweep = EmptySquare;
7311         } else UnLoadPV(); // [HGM] pv
7312     }
7313     if (action != Press) return -2; // return code to be ignored
7314     switch (gameMode) {
7315       case IcsExamining:
7316         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7317       case EditPosition:
7318         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7319         if (xSqr < 0 || ySqr < 0) return -1;
7320         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7321         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7322         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7323         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7324         NextPiece(0);
7325         return 2; // grab
7326       case IcsObserving:
7327         if(!appData.icsEngineAnalyze) return -1;
7328       case IcsPlayingWhite:
7329       case IcsPlayingBlack:
7330         if(!appData.zippyPlay) goto noZip;
7331       case AnalyzeMode:
7332       case AnalyzeFile:
7333       case MachinePlaysWhite:
7334       case MachinePlaysBlack:
7335       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7336         if (!appData.dropMenu) {
7337           LoadPV(x, y);
7338           return 2; // flag front-end to grab mouse events
7339         }
7340         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7341            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7342       case EditGame:
7343       noZip:
7344         if (xSqr < 0 || ySqr < 0) return -1;
7345         if (!appData.dropMenu || appData.testLegality &&
7346             gameInfo.variant != VariantBughouse &&
7347             gameInfo.variant != VariantCrazyhouse) return -1;
7348         whichMenu = 1; // drop menu
7349         break;
7350       default:
7351         return -1;
7352     }
7353
7354     if (((*fromX = xSqr) < 0) ||
7355         ((*fromY = ySqr) < 0)) {
7356         *fromX = *fromY = -1;
7357         return -1;
7358     }
7359     if (flipView)
7360       *fromX = BOARD_WIDTH - 1 - *fromX;
7361     else
7362       *fromY = BOARD_HEIGHT - 1 - *fromY;
7363
7364     return whichMenu;
7365 }
7366
7367 void
7368 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7369 {
7370 //    char * hint = lastHint;
7371     FrontEndProgramStats stats;
7372
7373     stats.which = cps == &first ? 0 : 1;
7374     stats.depth = cpstats->depth;
7375     stats.nodes = cpstats->nodes;
7376     stats.score = cpstats->score;
7377     stats.time = cpstats->time;
7378     stats.pv = cpstats->movelist;
7379     stats.hint = lastHint;
7380     stats.an_move_index = 0;
7381     stats.an_move_count = 0;
7382
7383     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7384         stats.hint = cpstats->move_name;
7385         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7386         stats.an_move_count = cpstats->nr_moves;
7387     }
7388
7389     if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
7390
7391     SetProgramStats( &stats );
7392 }
7393
7394 void
7395 ClearEngineOutputPane (int which)
7396 {
7397     static FrontEndProgramStats dummyStats;
7398     dummyStats.which = which;
7399     dummyStats.pv = "#";
7400     SetProgramStats( &dummyStats );
7401 }
7402
7403 #define MAXPLAYERS 500
7404
7405 char *
7406 TourneyStandings (int display)
7407 {
7408     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7409     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7410     char result, *p, *names[MAXPLAYERS];
7411
7412     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7413         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7414     names[0] = p = strdup(appData.participants);
7415     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7416
7417     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7418
7419     while(result = appData.results[nr]) {
7420         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7421         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7422         wScore = bScore = 0;
7423         switch(result) {
7424           case '+': wScore = 2; break;
7425           case '-': bScore = 2; break;
7426           case '=': wScore = bScore = 1; break;
7427           case ' ':
7428           case '*': return strdup("busy"); // tourney not finished
7429         }
7430         score[w] += wScore;
7431         score[b] += bScore;
7432         games[w]++;
7433         games[b]++;
7434         nr++;
7435     }
7436     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7437     for(w=0; w<nPlayers; w++) {
7438         bScore = -1;
7439         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7440         ranking[w] = b; points[w] = bScore; score[b] = -2;
7441     }
7442     p = malloc(nPlayers*34+1);
7443     for(w=0; w<nPlayers && w<display; w++)
7444         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7445     free(names[0]);
7446     return p;
7447 }
7448
7449 void
7450 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7451 {       // count all piece types
7452         int p, f, r;
7453         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7454         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7455         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7456                 p = board[r][f];
7457                 pCnt[p]++;
7458                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7459                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7460                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7461                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7462                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7463                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7464         }
7465 }
7466
7467 int
7468 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7469 {
7470         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7471         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7472
7473         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7474         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7475         if(myPawns == 2 && nMine == 3) // KPP
7476             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7477         if(myPawns == 1 && nMine == 2) // KP
7478             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7479         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7480             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7481         if(myPawns) return FALSE;
7482         if(pCnt[WhiteRook+side])
7483             return pCnt[BlackRook-side] ||
7484                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7485                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7486                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7487         if(pCnt[WhiteCannon+side]) {
7488             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7489             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7490         }
7491         if(pCnt[WhiteKnight+side])
7492             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7493         return FALSE;
7494 }
7495
7496 int
7497 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7498 {
7499         VariantClass v = gameInfo.variant;
7500
7501         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7502         if(v == VariantShatranj) return TRUE; // always winnable through baring
7503         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7504         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7505
7506         if(v == VariantXiangqi) {
7507                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7508
7509                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7510                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7511                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7512                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7513                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7514                 if(stale) // we have at least one last-rank P plus perhaps C
7515                     return majors // KPKX
7516                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7517                 else // KCA*E*
7518                     return pCnt[WhiteFerz+side] // KCAK
7519                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7520                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7521                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7522
7523         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7524                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7525
7526                 if(nMine == 1) return FALSE; // bare King
7527                 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
7528                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7529                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7530                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7531                 if(pCnt[WhiteKnight+side])
7532                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7533                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7534                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7535                 if(nBishops)
7536                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7537                 if(pCnt[WhiteAlfil+side])
7538                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7539                 if(pCnt[WhiteWazir+side])
7540                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7541         }
7542
7543         return TRUE;
7544 }
7545
7546 int
7547 CompareWithRights (Board b1, Board b2)
7548 {
7549     int rights = 0;
7550     if(!CompareBoards(b1, b2)) return FALSE;
7551     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7552     /* compare castling rights */
7553     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7554            rights++; /* King lost rights, while rook still had them */
7555     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7556         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7557            rights++; /* but at least one rook lost them */
7558     }
7559     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7560            rights++;
7561     if( b1[CASTLING][5] != NoRights ) {
7562         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7563            rights++;
7564     }
7565     return rights == 0;
7566 }
7567
7568 int
7569 Adjudicate (ChessProgramState *cps)
7570 {       // [HGM] some adjudications useful with buggy engines
7571         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7572         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7573         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7574         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7575         int k, count = 0; static int bare = 1;
7576         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7577         Boolean canAdjudicate = !appData.icsActive;
7578
7579         // most tests only when we understand the game, i.e. legality-checking on
7580             if( appData.testLegality )
7581             {   /* [HGM] Some more adjudications for obstinate engines */
7582                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7583                 static int moveCount = 6;
7584                 ChessMove result;
7585                 char *reason = NULL;
7586
7587                 /* Count what is on board. */
7588                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7589
7590                 /* Some material-based adjudications that have to be made before stalemate test */
7591                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7592                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7593                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7594                      if(canAdjudicate && appData.checkMates) {
7595                          if(engineOpponent)
7596                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7597                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7598                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7599                          return 1;
7600                      }
7601                 }
7602
7603                 /* Bare King in Shatranj (loses) or Losers (wins) */
7604                 if( nrW == 1 || nrB == 1) {
7605                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7606                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7607                      if(canAdjudicate && appData.checkMates) {
7608                          if(engineOpponent)
7609                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7610                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7611                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7612                          return 1;
7613                      }
7614                   } else
7615                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7616                   {    /* bare King */
7617                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7618                         if(canAdjudicate && appData.checkMates) {
7619                             /* but only adjudicate if adjudication enabled */
7620                             if(engineOpponent)
7621                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7622                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7623                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7624                             return 1;
7625                         }
7626                   }
7627                 } else bare = 1;
7628
7629
7630             // don't wait for engine to announce game end if we can judge ourselves
7631             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7632               case MT_CHECK:
7633                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7634                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7635                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7636                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7637                             checkCnt++;
7638                         if(checkCnt >= 2) {
7639                             reason = "Xboard adjudication: 3rd check";
7640                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7641                             break;
7642                         }
7643                     }
7644                 }
7645               case MT_NONE:
7646               default:
7647                 break;
7648               case MT_STALEMATE:
7649               case MT_STAINMATE:
7650                 reason = "Xboard adjudication: Stalemate";
7651                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7652                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7653                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7654                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7655                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7656                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7657                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7658                                                                         EP_CHECKMATE : EP_WINS);
7659                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7660                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7661                 }
7662                 break;
7663               case MT_CHECKMATE:
7664                 reason = "Xboard adjudication: Checkmate";
7665                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7666                 break;
7667             }
7668
7669                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7670                     case EP_STALEMATE:
7671                         result = GameIsDrawn; break;
7672                     case EP_CHECKMATE:
7673                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7674                     case EP_WINS:
7675                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7676                     default:
7677                         result = EndOfFile;
7678                 }
7679                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7680                     if(engineOpponent)
7681                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7682                     GameEnds( result, reason, GE_XBOARD );
7683                     return 1;
7684                 }
7685
7686                 /* Next absolutely insufficient mating material. */
7687                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7688                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7689                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7690
7691                      /* always flag draws, for judging claims */
7692                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7693
7694                      if(canAdjudicate && appData.materialDraws) {
7695                          /* but only adjudicate them if adjudication enabled */
7696                          if(engineOpponent) {
7697                            SendToProgram("force\n", engineOpponent); // suppress reply
7698                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7699                          }
7700                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7701                          return 1;
7702                      }
7703                 }
7704
7705                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7706                 if(gameInfo.variant == VariantXiangqi ?
7707                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7708                  : nrW + nrB == 4 &&
7709                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7710                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7711                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7712                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7713                    ) ) {
7714                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7715                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7716                           if(engineOpponent) {
7717                             SendToProgram("force\n", engineOpponent); // suppress reply
7718                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7719                           }
7720                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7721                           return 1;
7722                      }
7723                 } else moveCount = 6;
7724             }
7725
7726         // Repetition draws and 50-move rule can be applied independently of legality testing
7727
7728                 /* Check for rep-draws */
7729                 count = 0;
7730                 for(k = forwardMostMove-2;
7731                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7732                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7733                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7734                     k-=2)
7735                 {   int rights=0;
7736                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7737                         /* compare castling rights */
7738                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7739                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7740                                 rights++; /* King lost rights, while rook still had them */
7741                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7742                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7743                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7744                                    rights++; /* but at least one rook lost them */
7745                         }
7746                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7747                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7748                                 rights++;
7749                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7750                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7751                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7752                                    rights++;
7753                         }
7754                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7755                             && appData.drawRepeats > 1) {
7756                              /* adjudicate after user-specified nr of repeats */
7757                              int result = GameIsDrawn;
7758                              char *details = "XBoard adjudication: repetition draw";
7759                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7760                                 // [HGM] xiangqi: check for forbidden perpetuals
7761                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7762                                 for(m=forwardMostMove; m>k; m-=2) {
7763                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7764                                         ourPerpetual = 0; // the current mover did not always check
7765                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7766                                         hisPerpetual = 0; // the opponent did not always check
7767                                 }
7768                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7769                                                                         ourPerpetual, hisPerpetual);
7770                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7771                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7772                                     details = "Xboard adjudication: perpetual checking";
7773                                 } else
7774                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7775                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7776                                 } else
7777                                 // Now check for perpetual chases
7778                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7779                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7780                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7781                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7782                                         static char resdet[MSG_SIZ];
7783                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7784                                         details = resdet;
7785                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7786                                     } else
7787                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7788                                         break; // Abort repetition-checking loop.
7789                                 }
7790                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7791                              }
7792                              if(engineOpponent) {
7793                                SendToProgram("force\n", engineOpponent); // suppress reply
7794                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7795                              }
7796                              GameEnds( result, details, GE_XBOARD );
7797                              return 1;
7798                         }
7799                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7800                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7801                     }
7802                 }
7803
7804                 /* Now we test for 50-move draws. Determine ply count */
7805                 count = forwardMostMove;
7806                 /* look for last irreversble move */
7807                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7808                     count--;
7809                 /* if we hit starting position, add initial plies */
7810                 if( count == backwardMostMove )
7811                     count -= initialRulePlies;
7812                 count = forwardMostMove - count;
7813                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7814                         // adjust reversible move counter for checks in Xiangqi
7815                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7816                         if(i < backwardMostMove) i = backwardMostMove;
7817                         while(i <= forwardMostMove) {
7818                                 lastCheck = inCheck; // check evasion does not count
7819                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7820                                 if(inCheck || lastCheck) count--; // check does not count
7821                                 i++;
7822                         }
7823                 }
7824                 if( count >= 100)
7825                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7826                          /* this is used to judge if draw claims are legal */
7827                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7828                          if(engineOpponent) {
7829                            SendToProgram("force\n", engineOpponent); // suppress reply
7830                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7831                          }
7832                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7833                          return 1;
7834                 }
7835
7836                 /* if draw offer is pending, treat it as a draw claim
7837                  * when draw condition present, to allow engines a way to
7838                  * claim draws before making their move to avoid a race
7839                  * condition occurring after their move
7840                  */
7841                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7842                          char *p = NULL;
7843                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7844                              p = "Draw claim: 50-move rule";
7845                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7846                              p = "Draw claim: 3-fold repetition";
7847                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7848                              p = "Draw claim: insufficient mating material";
7849                          if( p != NULL && canAdjudicate) {
7850                              if(engineOpponent) {
7851                                SendToProgram("force\n", engineOpponent); // suppress reply
7852                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7853                              }
7854                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7855                              return 1;
7856                          }
7857                 }
7858
7859                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7860                     if(engineOpponent) {
7861                       SendToProgram("force\n", engineOpponent); // suppress reply
7862                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7863                     }
7864                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7865                     return 1;
7866                 }
7867         return 0;
7868 }
7869
7870 char *
7871 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7872 {   // [HGM] book: this routine intercepts moves to simulate book replies
7873     char *bookHit = NULL;
7874
7875     //first determine if the incoming move brings opponent into his book
7876     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7877         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7878     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7879     if(bookHit != NULL && !cps->bookSuspend) {
7880         // make sure opponent is not going to reply after receiving move to book position
7881         SendToProgram("force\n", cps);
7882         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7883     }
7884     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7885     // now arrange restart after book miss
7886     if(bookHit) {
7887         // after a book hit we never send 'go', and the code after the call to this routine
7888         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7889         char buf[MSG_SIZ], *move = bookHit;
7890         if(cps->useSAN) {
7891             int fromX, fromY, toX, toY;
7892             char promoChar;
7893             ChessMove moveType;
7894             move = buf + 30;
7895             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7896                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7897                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7898                                     PosFlags(forwardMostMove),
7899                                     fromY, fromX, toY, toX, promoChar, move);
7900             } else {
7901                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7902                 bookHit = NULL;
7903             }
7904         }
7905         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7906         SendToProgram(buf, cps);
7907         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7908     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7909         SendToProgram("go\n", cps);
7910         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7911     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7912         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7913             SendToProgram("go\n", cps);
7914         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7915     }
7916     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7917 }
7918
7919 char *savedMessage;
7920 ChessProgramState *savedState;
7921 void
7922 DeferredBookMove (void)
7923 {
7924         if(savedState->lastPing != savedState->lastPong)
7925                     ScheduleDelayedEvent(DeferredBookMove, 10);
7926         else
7927         HandleMachineMove(savedMessage, savedState);
7928 }
7929
7930 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7931
7932 void
7933 HandleMachineMove (char *message, ChessProgramState *cps)
7934 {
7935     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7936     char realname[MSG_SIZ];
7937     int fromX, fromY, toX, toY;
7938     ChessMove moveType;
7939     char promoChar;
7940     char *p, *pv=buf1;
7941     int machineWhite;
7942     char *bookHit;
7943
7944     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7945         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7946         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7947             DisplayError(_("Invalid pairing from pairing engine"), 0);
7948             return;
7949         }
7950         pairingReceived = 1;
7951         NextMatchGame();
7952         return; // Skim the pairing messages here.
7953     }
7954
7955     cps->userError = 0;
7956
7957 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7958     /*
7959      * Kludge to ignore BEL characters
7960      */
7961     while (*message == '\007') message++;
7962
7963     /*
7964      * [HGM] engine debug message: ignore lines starting with '#' character
7965      */
7966     if(cps->debug && *message == '#') return;
7967
7968     /*
7969      * Look for book output
7970      */
7971     if (cps == &first && bookRequested) {
7972         if (message[0] == '\t' || message[0] == ' ') {
7973             /* Part of the book output is here; append it */
7974             strcat(bookOutput, message);
7975             strcat(bookOutput, "  \n");
7976             return;
7977         } else if (bookOutput[0] != NULLCHAR) {
7978             /* All of book output has arrived; display it */
7979             char *p = bookOutput;
7980             while (*p != NULLCHAR) {
7981                 if (*p == '\t') *p = ' ';
7982                 p++;
7983             }
7984             DisplayInformation(bookOutput);
7985             bookRequested = FALSE;
7986             /* Fall through to parse the current output */
7987         }
7988     }
7989
7990     /*
7991      * Look for machine move.
7992      */
7993     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7994         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7995     {
7996         /* This method is only useful on engines that support ping */
7997         if (cps->lastPing != cps->lastPong) {
7998           if (gameMode == BeginningOfGame) {
7999             /* Extra move from before last new; ignore */
8000             if (appData.debugMode) {
8001                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8002             }
8003           } else {
8004             if (appData.debugMode) {
8005                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8006                         cps->which, gameMode);
8007             }
8008
8009             SendToProgram("undo\n", cps);
8010           }
8011           return;
8012         }
8013
8014         switch (gameMode) {
8015           case BeginningOfGame:
8016             /* Extra move from before last reset; ignore */
8017             if (appData.debugMode) {
8018                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8019             }
8020             return;
8021
8022           case EndOfGame:
8023           case IcsIdle:
8024           default:
8025             /* Extra move after we tried to stop.  The mode test is
8026                not a reliable way of detecting this problem, but it's
8027                the best we can do on engines that don't support ping.
8028             */
8029             if (appData.debugMode) {
8030                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8031                         cps->which, gameMode);
8032             }
8033             SendToProgram("undo\n", cps);
8034             return;
8035
8036           case MachinePlaysWhite:
8037           case IcsPlayingWhite:
8038             machineWhite = TRUE;
8039             break;
8040
8041           case MachinePlaysBlack:
8042           case IcsPlayingBlack:
8043             machineWhite = FALSE;
8044             break;
8045
8046           case TwoMachinesPlay:
8047             machineWhite = (cps->twoMachinesColor[0] == 'w');
8048             break;
8049         }
8050         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8051             if (appData.debugMode) {
8052                 fprintf(debugFP,
8053                         "Ignoring move out of turn by %s, gameMode %d"
8054                         ", forwardMost %d\n",
8055                         cps->which, gameMode, forwardMostMove);
8056             }
8057             return;
8058         }
8059
8060         if(cps->alphaRank) AlphaRank(machineMove, 4);
8061         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8062                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8063             /* Machine move could not be parsed; ignore it. */
8064           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8065                     machineMove, _(cps->which));
8066             DisplayError(buf1, 0);
8067             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8068                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8069             if (gameMode == TwoMachinesPlay) {
8070               GameEnds(machineWhite ? BlackWins : WhiteWins,
8071                        buf1, GE_XBOARD);
8072             }
8073             return;
8074         }
8075
8076         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8077         /* So we have to redo legality test with true e.p. status here,  */
8078         /* to make sure an illegal e.p. capture does not slip through,   */
8079         /* to cause a forfeit on a justified illegal-move complaint      */
8080         /* of the opponent.                                              */
8081         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8082            ChessMove moveType;
8083            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8084                              fromY, fromX, toY, toX, promoChar);
8085             if(moveType == IllegalMove) {
8086               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8087                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8088                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8089                            buf1, GE_XBOARD);
8090                 return;
8091            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8092            /* [HGM] Kludge to handle engines that send FRC-style castling
8093               when they shouldn't (like TSCP-Gothic) */
8094            switch(moveType) {
8095              case WhiteASideCastleFR:
8096              case BlackASideCastleFR:
8097                toX+=2;
8098                currentMoveString[2]++;
8099                break;
8100              case WhiteHSideCastleFR:
8101              case BlackHSideCastleFR:
8102                toX--;
8103                currentMoveString[2]--;
8104                break;
8105              default: ; // nothing to do, but suppresses warning of pedantic compilers
8106            }
8107         }
8108         hintRequested = FALSE;
8109         lastHint[0] = NULLCHAR;
8110         bookRequested = FALSE;
8111         /* Program may be pondering now */
8112         cps->maybeThinking = TRUE;
8113         if (cps->sendTime == 2) cps->sendTime = 1;
8114         if (cps->offeredDraw) cps->offeredDraw--;
8115
8116         /* [AS] Save move info*/
8117         pvInfoList[ forwardMostMove ].score = programStats.score;
8118         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8119         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8120
8121         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8122
8123         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8124         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8125             int count = 0;
8126
8127             while( count < adjudicateLossPlies ) {
8128                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8129
8130                 if( count & 1 ) {
8131                     score = -score; /* Flip score for winning side */
8132                 }
8133
8134                 if( score > adjudicateLossThreshold ) {
8135                     break;
8136                 }
8137
8138                 count++;
8139             }
8140
8141             if( count >= adjudicateLossPlies ) {
8142                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8143
8144                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8145                     "Xboard adjudication",
8146                     GE_XBOARD );
8147
8148                 return;
8149             }
8150         }
8151
8152         if(Adjudicate(cps)) {
8153             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8154             return; // [HGM] adjudicate: for all automatic game ends
8155         }
8156
8157 #if ZIPPY
8158         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8159             first.initDone) {
8160           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8161                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8162                 SendToICS("draw ");
8163                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8164           }
8165           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8166           ics_user_moved = 1;
8167           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8168                 char buf[3*MSG_SIZ];
8169
8170                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8171                         programStats.score / 100.,
8172                         programStats.depth,
8173                         programStats.time / 100.,
8174                         (unsigned int)programStats.nodes,
8175                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8176                         programStats.movelist);
8177                 SendToICS(buf);
8178 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8179           }
8180         }
8181 #endif
8182
8183         /* [AS] Clear stats for next move */
8184         ClearProgramStats();
8185         thinkOutput[0] = NULLCHAR;
8186         hiddenThinkOutputState = 0;
8187
8188         bookHit = NULL;
8189         if (gameMode == TwoMachinesPlay) {
8190             /* [HGM] relaying draw offers moved to after reception of move */
8191             /* and interpreting offer as claim if it brings draw condition */
8192             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8193                 SendToProgram("draw\n", cps->other);
8194             }
8195             if (cps->other->sendTime) {
8196                 SendTimeRemaining(cps->other,
8197                                   cps->other->twoMachinesColor[0] == 'w');
8198             }
8199             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8200             if (firstMove && !bookHit) {
8201                 firstMove = FALSE;
8202                 if (cps->other->useColors) {
8203                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8204                 }
8205                 SendToProgram("go\n", cps->other);
8206             }
8207             cps->other->maybeThinking = TRUE;
8208         }
8209
8210         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8211
8212         if (!pausing && appData.ringBellAfterMoves) {
8213             RingBell();
8214         }
8215
8216         /*
8217          * Reenable menu items that were disabled while
8218          * machine was thinking
8219          */
8220         if (gameMode != TwoMachinesPlay)
8221             SetUserThinkingEnables();
8222
8223         // [HGM] book: after book hit opponent has received move and is now in force mode
8224         // force the book reply into it, and then fake that it outputted this move by jumping
8225         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8226         if(bookHit) {
8227                 static char bookMove[MSG_SIZ]; // a bit generous?
8228
8229                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8230                 strcat(bookMove, bookHit);
8231                 message = bookMove;
8232                 cps = cps->other;
8233                 programStats.nodes = programStats.depth = programStats.time =
8234                 programStats.score = programStats.got_only_move = 0;
8235                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8236
8237                 if(cps->lastPing != cps->lastPong) {
8238                     savedMessage = message; // args for deferred call
8239                     savedState = cps;
8240                     ScheduleDelayedEvent(DeferredBookMove, 10);
8241                     return;
8242                 }
8243                 goto FakeBookMove;
8244         }
8245
8246         return;
8247     }
8248
8249     /* Set special modes for chess engines.  Later something general
8250      *  could be added here; for now there is just one kludge feature,
8251      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8252      *  when "xboard" is given as an interactive command.
8253      */
8254     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8255         cps->useSigint = FALSE;
8256         cps->useSigterm = FALSE;
8257     }
8258     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8259       ParseFeatures(message+8, cps);
8260       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8261     }
8262
8263     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8264                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8265       int dummy, s=6; char buf[MSG_SIZ];
8266       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8267       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8268       if(startedFromSetupPosition) return;
8269       ParseFEN(boards[0], &dummy, message+s);
8270       DrawPosition(TRUE, boards[0]);
8271       startedFromSetupPosition = TRUE;
8272       return;
8273     }
8274     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8275      * want this, I was asked to put it in, and obliged.
8276      */
8277     if (!strncmp(message, "setboard ", 9)) {
8278         Board initial_position;
8279
8280         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8281
8282         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8283             DisplayError(_("Bad FEN received from engine"), 0);
8284             return ;
8285         } else {
8286            Reset(TRUE, FALSE);
8287            CopyBoard(boards[0], initial_position);
8288            initialRulePlies = FENrulePlies;
8289            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8290            else gameMode = MachinePlaysBlack;
8291            DrawPosition(FALSE, boards[currentMove]);
8292         }
8293         return;
8294     }
8295
8296     /*
8297      * Look for communication commands
8298      */
8299     if (!strncmp(message, "telluser ", 9)) {
8300         if(message[9] == '\\' && message[10] == '\\')
8301             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8302         PlayTellSound();
8303         DisplayNote(message + 9);
8304         return;
8305     }
8306     if (!strncmp(message, "tellusererror ", 14)) {
8307         cps->userError = 1;
8308         if(message[14] == '\\' && message[15] == '\\')
8309             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8310         PlayTellSound();
8311         DisplayError(message + 14, 0);
8312         return;
8313     }
8314     if (!strncmp(message, "tellopponent ", 13)) {
8315       if (appData.icsActive) {
8316         if (loggedOn) {
8317           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8318           SendToICS(buf1);
8319         }
8320       } else {
8321         DisplayNote(message + 13);
8322       }
8323       return;
8324     }
8325     if (!strncmp(message, "tellothers ", 11)) {
8326       if (appData.icsActive) {
8327         if (loggedOn) {
8328           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8329           SendToICS(buf1);
8330         }
8331       }
8332       return;
8333     }
8334     if (!strncmp(message, "tellall ", 8)) {
8335       if (appData.icsActive) {
8336         if (loggedOn) {
8337           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8338           SendToICS(buf1);
8339         }
8340       } else {
8341         DisplayNote(message + 8);
8342       }
8343       return;
8344     }
8345     if (strncmp(message, "warning", 7) == 0) {
8346         /* Undocumented feature, use tellusererror in new code */
8347         DisplayError(message, 0);
8348         return;
8349     }
8350     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8351         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8352         strcat(realname, " query");
8353         AskQuestion(realname, buf2, buf1, cps->pr);
8354         return;
8355     }
8356     /* Commands from the engine directly to ICS.  We don't allow these to be
8357      *  sent until we are logged on. Crafty kibitzes have been known to
8358      *  interfere with the login process.
8359      */
8360     if (loggedOn) {
8361         if (!strncmp(message, "tellics ", 8)) {
8362             SendToICS(message + 8);
8363             SendToICS("\n");
8364             return;
8365         }
8366         if (!strncmp(message, "tellicsnoalias ", 15)) {
8367             SendToICS(ics_prefix);
8368             SendToICS(message + 15);
8369             SendToICS("\n");
8370             return;
8371         }
8372         /* The following are for backward compatibility only */
8373         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8374             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8375             SendToICS(ics_prefix);
8376             SendToICS(message);
8377             SendToICS("\n");
8378             return;
8379         }
8380     }
8381     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8382         return;
8383     }
8384     /*
8385      * If the move is illegal, cancel it and redraw the board.
8386      * Also deal with other error cases.  Matching is rather loose
8387      * here to accommodate engines written before the spec.
8388      */
8389     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8390         strncmp(message, "Error", 5) == 0) {
8391         if (StrStr(message, "name") ||
8392             StrStr(message, "rating") || StrStr(message, "?") ||
8393             StrStr(message, "result") || StrStr(message, "board") ||
8394             StrStr(message, "bk") || StrStr(message, "computer") ||
8395             StrStr(message, "variant") || StrStr(message, "hint") ||
8396             StrStr(message, "random") || StrStr(message, "depth") ||
8397             StrStr(message, "accepted")) {
8398             return;
8399         }
8400         if (StrStr(message, "protover")) {
8401           /* Program is responding to input, so it's apparently done
8402              initializing, and this error message indicates it is
8403              protocol version 1.  So we don't need to wait any longer
8404              for it to initialize and send feature commands. */
8405           FeatureDone(cps, 1);
8406           cps->protocolVersion = 1;
8407           return;
8408         }
8409         cps->maybeThinking = FALSE;
8410
8411         if (StrStr(message, "draw")) {
8412             /* Program doesn't have "draw" command */
8413             cps->sendDrawOffers = 0;
8414             return;
8415         }
8416         if (cps->sendTime != 1 &&
8417             (StrStr(message, "time") || StrStr(message, "otim"))) {
8418           /* Program apparently doesn't have "time" or "otim" command */
8419           cps->sendTime = 0;
8420           return;
8421         }
8422         if (StrStr(message, "analyze")) {
8423             cps->analysisSupport = FALSE;
8424             cps->analyzing = FALSE;
8425 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8426             EditGameEvent(); // [HGM] try to preserve loaded game
8427             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8428             DisplayError(buf2, 0);
8429             return;
8430         }
8431         if (StrStr(message, "(no matching move)st")) {
8432           /* Special kludge for GNU Chess 4 only */
8433           cps->stKludge = TRUE;
8434           SendTimeControl(cps, movesPerSession, timeControl,
8435                           timeIncrement, appData.searchDepth,
8436                           searchTime);
8437           return;
8438         }
8439         if (StrStr(message, "(no matching move)sd")) {
8440           /* Special kludge for GNU Chess 4 only */
8441           cps->sdKludge = TRUE;
8442           SendTimeControl(cps, movesPerSession, timeControl,
8443                           timeIncrement, appData.searchDepth,
8444                           searchTime);
8445           return;
8446         }
8447         if (!StrStr(message, "llegal")) {
8448             return;
8449         }
8450         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8451             gameMode == IcsIdle) return;
8452         if (forwardMostMove <= backwardMostMove) return;
8453         if (pausing) PauseEvent();
8454       if(appData.forceIllegal) {
8455             // [HGM] illegal: machine refused move; force position after move into it
8456           SendToProgram("force\n", cps);
8457           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8458                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8459                 // when black is to move, while there might be nothing on a2 or black
8460                 // might already have the move. So send the board as if white has the move.
8461                 // But first we must change the stm of the engine, as it refused the last move
8462                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8463                 if(WhiteOnMove(forwardMostMove)) {
8464                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8465                     SendBoard(cps, forwardMostMove); // kludgeless board
8466                 } else {
8467                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8468                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8469                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8470                 }
8471           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8472             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8473                  gameMode == TwoMachinesPlay)
8474               SendToProgram("go\n", cps);
8475             return;
8476       } else
8477         if (gameMode == PlayFromGameFile) {
8478             /* Stop reading this game file */
8479             gameMode = EditGame;
8480             ModeHighlight();
8481         }
8482         /* [HGM] illegal-move claim should forfeit game when Xboard */
8483         /* only passes fully legal moves                            */
8484         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8485             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8486                                 "False illegal-move claim", GE_XBOARD );
8487             return; // do not take back move we tested as valid
8488         }
8489         currentMove = forwardMostMove-1;
8490         DisplayMove(currentMove-1); /* before DisplayMoveError */
8491         SwitchClocks(forwardMostMove-1); // [HGM] race
8492         DisplayBothClocks();
8493         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8494                 parseList[currentMove], _(cps->which));
8495         DisplayMoveError(buf1);
8496         DrawPosition(FALSE, boards[currentMove]);
8497
8498         SetUserThinkingEnables();
8499         return;
8500     }
8501     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8502         /* Program has a broken "time" command that
8503            outputs a string not ending in newline.
8504            Don't use it. */
8505         cps->sendTime = 0;
8506     }
8507
8508     /*
8509      * If chess program startup fails, exit with an error message.
8510      * Attempts to recover here are futile. [HGM] Well, we try anyway
8511      */
8512     if ((StrStr(message, "unknown host") != NULL)
8513         || (StrStr(message, "No remote directory") != NULL)
8514         || (StrStr(message, "not found") != NULL)
8515         || (StrStr(message, "No such file") != NULL)
8516         || (StrStr(message, "can't alloc") != NULL)
8517         || (StrStr(message, "Permission denied") != NULL)) {
8518
8519         cps->maybeThinking = FALSE;
8520         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8521                 _(cps->which), cps->program, cps->host, message);
8522         RemoveInputSource(cps->isr);
8523         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8524             cps->isr = NULL;
8525             DestroyChildProcess(cps->pr, 9 ); // just to be sure
8526             cps->pr = NoProc; 
8527             if(cps == &first) {
8528                 appData.noChessProgram = TRUE;
8529                 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8530                 gameMode = BeginningOfGame; ModeHighlight();
8531                 SetNCPMode();
8532             }
8533             if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8534             DisplayMessage("", ""); // erase waiting message
8535             DisplayError(buf1, 0);
8536         }
8537         return;
8538     }
8539
8540     /*
8541      * Look for hint output
8542      */
8543     if (sscanf(message, "Hint: %s", buf1) == 1) {
8544         if (cps == &first && hintRequested) {
8545             hintRequested = FALSE;
8546             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8547                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8548                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8549                                     PosFlags(forwardMostMove),
8550                                     fromY, fromX, toY, toX, promoChar, buf1);
8551                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8552                 DisplayInformation(buf2);
8553             } else {
8554                 /* Hint move could not be parsed!? */
8555               snprintf(buf2, sizeof(buf2),
8556                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8557                         buf1, _(cps->which));
8558                 DisplayError(buf2, 0);
8559             }
8560         } else {
8561           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8562         }
8563         return;
8564     }
8565
8566     /*
8567      * Ignore other messages if game is not in progress
8568      */
8569     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8570         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8571
8572     /*
8573      * look for win, lose, draw, or draw offer
8574      */
8575     if (strncmp(message, "1-0", 3) == 0) {
8576         char *p, *q, *r = "";
8577         p = strchr(message, '{');
8578         if (p) {
8579             q = strchr(p, '}');
8580             if (q) {
8581                 *q = NULLCHAR;
8582                 r = p + 1;
8583             }
8584         }
8585         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8586         return;
8587     } else if (strncmp(message, "0-1", 3) == 0) {
8588         char *p, *q, *r = "";
8589         p = strchr(message, '{');
8590         if (p) {
8591             q = strchr(p, '}');
8592             if (q) {
8593                 *q = NULLCHAR;
8594                 r = p + 1;
8595             }
8596         }
8597         /* Kludge for Arasan 4.1 bug */
8598         if (strcmp(r, "Black resigns") == 0) {
8599             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8600             return;
8601         }
8602         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8603         return;
8604     } else if (strncmp(message, "1/2", 3) == 0) {
8605         char *p, *q, *r = "";
8606         p = strchr(message, '{');
8607         if (p) {
8608             q = strchr(p, '}');
8609             if (q) {
8610                 *q = NULLCHAR;
8611                 r = p + 1;
8612             }
8613         }
8614
8615         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8616         return;
8617
8618     } else if (strncmp(message, "White resign", 12) == 0) {
8619         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8620         return;
8621     } else if (strncmp(message, "Black resign", 12) == 0) {
8622         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8623         return;
8624     } else if (strncmp(message, "White matches", 13) == 0 ||
8625                strncmp(message, "Black matches", 13) == 0   ) {
8626         /* [HGM] ignore GNUShogi noises */
8627         return;
8628     } else if (strncmp(message, "White", 5) == 0 &&
8629                message[5] != '(' &&
8630                StrStr(message, "Black") == NULL) {
8631         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8632         return;
8633     } else if (strncmp(message, "Black", 5) == 0 &&
8634                message[5] != '(') {
8635         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8636         return;
8637     } else if (strcmp(message, "resign") == 0 ||
8638                strcmp(message, "computer resigns") == 0) {
8639         switch (gameMode) {
8640           case MachinePlaysBlack:
8641           case IcsPlayingBlack:
8642             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8643             break;
8644           case MachinePlaysWhite:
8645           case IcsPlayingWhite:
8646             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8647             break;
8648           case TwoMachinesPlay:
8649             if (cps->twoMachinesColor[0] == 'w')
8650               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8651             else
8652               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8653             break;
8654           default:
8655             /* can't happen */
8656             break;
8657         }
8658         return;
8659     } else if (strncmp(message, "opponent mates", 14) == 0) {
8660         switch (gameMode) {
8661           case MachinePlaysBlack:
8662           case IcsPlayingBlack:
8663             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8664             break;
8665           case MachinePlaysWhite:
8666           case IcsPlayingWhite:
8667             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8668             break;
8669           case TwoMachinesPlay:
8670             if (cps->twoMachinesColor[0] == 'w')
8671               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8672             else
8673               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8674             break;
8675           default:
8676             /* can't happen */
8677             break;
8678         }
8679         return;
8680     } else if (strncmp(message, "computer mates", 14) == 0) {
8681         switch (gameMode) {
8682           case MachinePlaysBlack:
8683           case IcsPlayingBlack:
8684             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8685             break;
8686           case MachinePlaysWhite:
8687           case IcsPlayingWhite:
8688             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8689             break;
8690           case TwoMachinesPlay:
8691             if (cps->twoMachinesColor[0] == 'w')
8692               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8693             else
8694               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8695             break;
8696           default:
8697             /* can't happen */
8698             break;
8699         }
8700         return;
8701     } else if (strncmp(message, "checkmate", 9) == 0) {
8702         if (WhiteOnMove(forwardMostMove)) {
8703             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8704         } else {
8705             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8706         }
8707         return;
8708     } else if (strstr(message, "Draw") != NULL ||
8709                strstr(message, "game is a draw") != NULL) {
8710         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8711         return;
8712     } else if (strstr(message, "offer") != NULL &&
8713                strstr(message, "draw") != NULL) {
8714 #if ZIPPY
8715         if (appData.zippyPlay && first.initDone) {
8716             /* Relay offer to ICS */
8717             SendToICS(ics_prefix);
8718             SendToICS("draw\n");
8719         }
8720 #endif
8721         cps->offeredDraw = 2; /* valid until this engine moves twice */
8722         if (gameMode == TwoMachinesPlay) {
8723             if (cps->other->offeredDraw) {
8724                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8725             /* [HGM] in two-machine mode we delay relaying draw offer      */
8726             /* until after we also have move, to see if it is really claim */
8727             }
8728         } else if (gameMode == MachinePlaysWhite ||
8729                    gameMode == MachinePlaysBlack) {
8730           if (userOfferedDraw) {
8731             DisplayInformation(_("Machine accepts your draw offer"));
8732             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8733           } else {
8734             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8735           }
8736         }
8737     }
8738
8739
8740     /*
8741      * Look for thinking output
8742      */
8743     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8744           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8745                                 ) {
8746         int plylev, mvleft, mvtot, curscore, time;
8747         char mvname[MOVE_LEN];
8748         u64 nodes; // [DM]
8749         char plyext;
8750         int ignore = FALSE;
8751         int prefixHint = FALSE;
8752         mvname[0] = NULLCHAR;
8753
8754         switch (gameMode) {
8755           case MachinePlaysBlack:
8756           case IcsPlayingBlack:
8757             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8758             break;
8759           case MachinePlaysWhite:
8760           case IcsPlayingWhite:
8761             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8762             break;
8763           case AnalyzeMode:
8764           case AnalyzeFile:
8765             break;
8766           case IcsObserving: /* [DM] icsEngineAnalyze */
8767             if (!appData.icsEngineAnalyze) ignore = TRUE;
8768             break;
8769           case TwoMachinesPlay:
8770             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8771                 ignore = TRUE;
8772             }
8773             break;
8774           default:
8775             ignore = TRUE;
8776             break;
8777         }
8778
8779         if (!ignore) {
8780             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8781             buf1[0] = NULLCHAR;
8782             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8783                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8784
8785                 if (plyext != ' ' && plyext != '\t') {
8786                     time *= 100;
8787                 }
8788
8789                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8790                 if( cps->scoreIsAbsolute &&
8791                     ( gameMode == MachinePlaysBlack ||
8792                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8793                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8794                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8795                      !WhiteOnMove(currentMove)
8796                     ) )
8797                 {
8798                     curscore = -curscore;
8799                 }
8800
8801                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8802
8803                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8804                         char buf[MSG_SIZ];
8805                         FILE *f;
8806                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8807                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8808                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8809                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8810                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8811                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8812                                 fclose(f);
8813                         } else DisplayError(_("failed writing PV"), 0);
8814                 }
8815
8816                 tempStats.depth = plylev;
8817                 tempStats.nodes = nodes;
8818                 tempStats.time = time;
8819                 tempStats.score = curscore;
8820                 tempStats.got_only_move = 0;
8821
8822                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8823                         int ticklen;
8824
8825                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8826                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8827                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8828                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8829                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8830                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8831                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8832                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8833                 }
8834
8835                 /* Buffer overflow protection */
8836                 if (pv[0] != NULLCHAR) {
8837                     if (strlen(pv) >= sizeof(tempStats.movelist)
8838                         && appData.debugMode) {
8839                         fprintf(debugFP,
8840                                 "PV is too long; using the first %u bytes.\n",
8841                                 (unsigned) sizeof(tempStats.movelist) - 1);
8842                     }
8843
8844                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8845                 } else {
8846                     sprintf(tempStats.movelist, " no PV\n");
8847                 }
8848
8849                 if (tempStats.seen_stat) {
8850                     tempStats.ok_to_send = 1;
8851                 }
8852
8853                 if (strchr(tempStats.movelist, '(') != NULL) {
8854                     tempStats.line_is_book = 1;
8855                     tempStats.nr_moves = 0;
8856                     tempStats.moves_left = 0;
8857                 } else {
8858                     tempStats.line_is_book = 0;
8859                 }
8860
8861                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8862                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8863
8864                 SendProgramStatsToFrontend( cps, &tempStats );
8865
8866                 /*
8867                     [AS] Protect the thinkOutput buffer from overflow... this
8868                     is only useful if buf1 hasn't overflowed first!
8869                 */
8870                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8871                          plylev,
8872                          (gameMode == TwoMachinesPlay ?
8873                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8874                          ((double) curscore) / 100.0,
8875                          prefixHint ? lastHint : "",
8876                          prefixHint ? " " : "" );
8877
8878                 if( buf1[0] != NULLCHAR ) {
8879                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8880
8881                     if( strlen(pv) > max_len ) {
8882                         if( appData.debugMode) {
8883                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8884                         }
8885                         pv[max_len+1] = '\0';
8886                     }
8887
8888                     strcat( thinkOutput, pv);
8889                 }
8890
8891                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8892                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8893                     DisplayMove(currentMove - 1);
8894                 }
8895                 return;
8896
8897             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8898                 /* crafty (9.25+) says "(only move) <move>"
8899                  * if there is only 1 legal move
8900                  */
8901                 sscanf(p, "(only move) %s", buf1);
8902                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8903                 sprintf(programStats.movelist, "%s (only move)", buf1);
8904                 programStats.depth = 1;
8905                 programStats.nr_moves = 1;
8906                 programStats.moves_left = 1;
8907                 programStats.nodes = 1;
8908                 programStats.time = 1;
8909                 programStats.got_only_move = 1;
8910
8911                 /* Not really, but we also use this member to
8912                    mean "line isn't going to change" (Crafty
8913                    isn't searching, so stats won't change) */
8914                 programStats.line_is_book = 1;
8915
8916                 SendProgramStatsToFrontend( cps, &programStats );
8917
8918                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8919                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8920                     DisplayMove(currentMove - 1);
8921                 }
8922                 return;
8923             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8924                               &time, &nodes, &plylev, &mvleft,
8925                               &mvtot, mvname) >= 5) {
8926                 /* The stat01: line is from Crafty (9.29+) in response
8927                    to the "." command */
8928                 programStats.seen_stat = 1;
8929                 cps->maybeThinking = TRUE;
8930
8931                 if (programStats.got_only_move || !appData.periodicUpdates)
8932                   return;
8933
8934                 programStats.depth = plylev;
8935                 programStats.time = time;
8936                 programStats.nodes = nodes;
8937                 programStats.moves_left = mvleft;
8938                 programStats.nr_moves = mvtot;
8939                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8940                 programStats.ok_to_send = 1;
8941                 programStats.movelist[0] = '\0';
8942
8943                 SendProgramStatsToFrontend( cps, &programStats );
8944
8945                 return;
8946
8947             } else if (strncmp(message,"++",2) == 0) {
8948                 /* Crafty 9.29+ outputs this */
8949                 programStats.got_fail = 2;
8950                 return;
8951
8952             } else if (strncmp(message,"--",2) == 0) {
8953                 /* Crafty 9.29+ outputs this */
8954                 programStats.got_fail = 1;
8955                 return;
8956
8957             } else if (thinkOutput[0] != NULLCHAR &&
8958                        strncmp(message, "    ", 4) == 0) {
8959                 unsigned message_len;
8960
8961                 p = message;
8962                 while (*p && *p == ' ') p++;
8963
8964                 message_len = strlen( p );
8965
8966                 /* [AS] Avoid buffer overflow */
8967                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8968                     strcat(thinkOutput, " ");
8969                     strcat(thinkOutput, p);
8970                 }
8971
8972                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8973                     strcat(programStats.movelist, " ");
8974                     strcat(programStats.movelist, p);
8975                 }
8976
8977                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8978                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8979                     DisplayMove(currentMove - 1);
8980                 }
8981                 return;
8982             }
8983         }
8984         else {
8985             buf1[0] = NULLCHAR;
8986
8987             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8988                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8989             {
8990                 ChessProgramStats cpstats;
8991
8992                 if (plyext != ' ' && plyext != '\t') {
8993                     time *= 100;
8994                 }
8995
8996                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8997                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8998                     curscore = -curscore;
8999                 }
9000
9001                 cpstats.depth = plylev;
9002                 cpstats.nodes = nodes;
9003                 cpstats.time = time;
9004                 cpstats.score = curscore;
9005                 cpstats.got_only_move = 0;
9006                 cpstats.movelist[0] = '\0';
9007
9008                 if (buf1[0] != NULLCHAR) {
9009                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9010                 }
9011
9012                 cpstats.ok_to_send = 0;
9013                 cpstats.line_is_book = 0;
9014                 cpstats.nr_moves = 0;
9015                 cpstats.moves_left = 0;
9016
9017                 SendProgramStatsToFrontend( cps, &cpstats );
9018             }
9019         }
9020     }
9021 }
9022
9023
9024 /* Parse a game score from the character string "game", and
9025    record it as the history of the current game.  The game
9026    score is NOT assumed to start from the standard position.
9027    The display is not updated in any way.
9028    */
9029 void
9030 ParseGameHistory (char *game)
9031 {
9032     ChessMove moveType;
9033     int fromX, fromY, toX, toY, boardIndex;
9034     char promoChar;
9035     char *p, *q;
9036     char buf[MSG_SIZ];
9037
9038     if (appData.debugMode)
9039       fprintf(debugFP, "Parsing game history: %s\n", game);
9040
9041     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9042     gameInfo.site = StrSave(appData.icsHost);
9043     gameInfo.date = PGNDate();
9044     gameInfo.round = StrSave("-");
9045
9046     /* Parse out names of players */
9047     while (*game == ' ') game++;
9048     p = buf;
9049     while (*game != ' ') *p++ = *game++;
9050     *p = NULLCHAR;
9051     gameInfo.white = StrSave(buf);
9052     while (*game == ' ') game++;
9053     p = buf;
9054     while (*game != ' ' && *game != '\n') *p++ = *game++;
9055     *p = NULLCHAR;
9056     gameInfo.black = StrSave(buf);
9057
9058     /* Parse moves */
9059     boardIndex = blackPlaysFirst ? 1 : 0;
9060     yynewstr(game);
9061     for (;;) {
9062         yyboardindex = boardIndex;
9063         moveType = (ChessMove) Myylex();
9064         switch (moveType) {
9065           case IllegalMove:             /* maybe suicide chess, etc. */
9066   if (appData.debugMode) {
9067     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9068     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9069     setbuf(debugFP, NULL);
9070   }
9071           case WhitePromotion:
9072           case BlackPromotion:
9073           case WhiteNonPromotion:
9074           case BlackNonPromotion:
9075           case NormalMove:
9076           case WhiteCapturesEnPassant:
9077           case BlackCapturesEnPassant:
9078           case WhiteKingSideCastle:
9079           case WhiteQueenSideCastle:
9080           case BlackKingSideCastle:
9081           case BlackQueenSideCastle:
9082           case WhiteKingSideCastleWild:
9083           case WhiteQueenSideCastleWild:
9084           case BlackKingSideCastleWild:
9085           case BlackQueenSideCastleWild:
9086           /* PUSH Fabien */
9087           case WhiteHSideCastleFR:
9088           case WhiteASideCastleFR:
9089           case BlackHSideCastleFR:
9090           case BlackASideCastleFR:
9091           /* POP Fabien */
9092             fromX = currentMoveString[0] - AAA;
9093             fromY = currentMoveString[1] - ONE;
9094             toX = currentMoveString[2] - AAA;
9095             toY = currentMoveString[3] - ONE;
9096             promoChar = currentMoveString[4];
9097             break;
9098           case WhiteDrop:
9099           case BlackDrop:
9100             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9101             fromX = moveType == WhiteDrop ?
9102               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9103             (int) CharToPiece(ToLower(currentMoveString[0]));
9104             fromY = DROP_RANK;
9105             toX = currentMoveString[2] - AAA;
9106             toY = currentMoveString[3] - ONE;
9107             promoChar = NULLCHAR;
9108             break;
9109           case AmbiguousMove:
9110             /* bug? */
9111             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9112   if (appData.debugMode) {
9113     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9114     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9115     setbuf(debugFP, NULL);
9116   }
9117             DisplayError(buf, 0);
9118             return;
9119           case ImpossibleMove:
9120             /* bug? */
9121             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9122   if (appData.debugMode) {
9123     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9124     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9125     setbuf(debugFP, NULL);
9126   }
9127             DisplayError(buf, 0);
9128             return;
9129           case EndOfFile:
9130             if (boardIndex < backwardMostMove) {
9131                 /* Oops, gap.  How did that happen? */
9132                 DisplayError(_("Gap in move list"), 0);
9133                 return;
9134             }
9135             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9136             if (boardIndex > forwardMostMove) {
9137                 forwardMostMove = boardIndex;
9138             }
9139             return;
9140           case ElapsedTime:
9141             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9142                 strcat(parseList[boardIndex-1], " ");
9143                 strcat(parseList[boardIndex-1], yy_text);
9144             }
9145             continue;
9146           case Comment:
9147           case PGNTag:
9148           case NAG:
9149           default:
9150             /* ignore */
9151             continue;
9152           case WhiteWins:
9153           case BlackWins:
9154           case GameIsDrawn:
9155           case GameUnfinished:
9156             if (gameMode == IcsExamining) {
9157                 if (boardIndex < backwardMostMove) {
9158                     /* Oops, gap.  How did that happen? */
9159                     return;
9160                 }
9161                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9162                 return;
9163             }
9164             gameInfo.result = moveType;
9165             p = strchr(yy_text, '{');
9166             if (p == NULL) p = strchr(yy_text, '(');
9167             if (p == NULL) {
9168                 p = yy_text;
9169                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9170             } else {
9171                 q = strchr(p, *p == '{' ? '}' : ')');
9172                 if (q != NULL) *q = NULLCHAR;
9173                 p++;
9174             }
9175             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9176             gameInfo.resultDetails = StrSave(p);
9177             continue;
9178         }
9179         if (boardIndex >= forwardMostMove &&
9180             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9181             backwardMostMove = blackPlaysFirst ? 1 : 0;
9182             return;
9183         }
9184         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9185                                  fromY, fromX, toY, toX, promoChar,
9186                                  parseList[boardIndex]);
9187         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9188         /* currentMoveString is set as a side-effect of yylex */
9189         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9190         strcat(moveList[boardIndex], "\n");
9191         boardIndex++;
9192         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9193         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9194           case MT_NONE:
9195           case MT_STALEMATE:
9196           default:
9197             break;
9198           case MT_CHECK:
9199             if(gameInfo.variant != VariantShogi)
9200                 strcat(parseList[boardIndex - 1], "+");
9201             break;
9202           case MT_CHECKMATE:
9203           case MT_STAINMATE:
9204             strcat(parseList[boardIndex - 1], "#");
9205             break;
9206         }
9207     }
9208 }
9209
9210
9211 /* Apply a move to the given board  */
9212 void
9213 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9214 {
9215   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9216   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9217
9218     /* [HGM] compute & store e.p. status and castling rights for new position */
9219     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9220
9221       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9222       oldEP = (signed char)board[EP_STATUS];
9223       board[EP_STATUS] = EP_NONE;
9224
9225   if (fromY == DROP_RANK) {
9226         /* must be first */
9227         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9228             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9229             return;
9230         }
9231         piece = board[toY][toX] = (ChessSquare) fromX;
9232   } else {
9233       int i;
9234
9235       if( board[toY][toX] != EmptySquare )
9236            board[EP_STATUS] = EP_CAPTURE;
9237
9238       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9239            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9240                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9241       } else
9242       if( board[fromY][fromX] == WhitePawn ) {
9243            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9244                board[EP_STATUS] = EP_PAWN_MOVE;
9245            if( toY-fromY==2) {
9246                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9247                         gameInfo.variant != VariantBerolina || toX < fromX)
9248                       board[EP_STATUS] = toX | berolina;
9249                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9250                         gameInfo.variant != VariantBerolina || toX > fromX)
9251                       board[EP_STATUS] = toX;
9252            }
9253       } else
9254       if( board[fromY][fromX] == BlackPawn ) {
9255            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9256                board[EP_STATUS] = EP_PAWN_MOVE;
9257            if( toY-fromY== -2) {
9258                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9259                         gameInfo.variant != VariantBerolina || toX < fromX)
9260                       board[EP_STATUS] = toX | berolina;
9261                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9262                         gameInfo.variant != VariantBerolina || toX > fromX)
9263                       board[EP_STATUS] = toX;
9264            }
9265        }
9266
9267        for(i=0; i<nrCastlingRights; i++) {
9268            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9269               board[CASTLING][i] == toX   && castlingRank[i] == toY
9270              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9271        }
9272
9273      if (fromX == toX && fromY == toY) return;
9274
9275      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9276      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9277      if(gameInfo.variant == VariantKnightmate)
9278          king += (int) WhiteUnicorn - (int) WhiteKing;
9279
9280     /* Code added by Tord: */
9281     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9282     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9283         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9284       board[fromY][fromX] = EmptySquare;
9285       board[toY][toX] = EmptySquare;
9286       if((toX > fromX) != (piece == WhiteRook)) {
9287         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9288       } else {
9289         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9290       }
9291     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9292                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9293       board[fromY][fromX] = EmptySquare;
9294       board[toY][toX] = EmptySquare;
9295       if((toX > fromX) != (piece == BlackRook)) {
9296         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9297       } else {
9298         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9299       }
9300     /* End of code added by Tord */
9301
9302     } else if (board[fromY][fromX] == king
9303         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9304         && toY == fromY && toX > fromX+1) {
9305         board[fromY][fromX] = EmptySquare;
9306         board[toY][toX] = king;
9307         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9308         board[fromY][BOARD_RGHT-1] = EmptySquare;
9309     } else if (board[fromY][fromX] == king
9310         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9311                && toY == fromY && toX < fromX-1) {
9312         board[fromY][fromX] = EmptySquare;
9313         board[toY][toX] = king;
9314         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9315         board[fromY][BOARD_LEFT] = EmptySquare;
9316     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9317                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9318                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9319                ) {
9320         /* white pawn promotion */
9321         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9322         if(gameInfo.variant==VariantBughouse ||
9323            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9324             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9325         board[fromY][fromX] = EmptySquare;
9326     } else if ((fromY >= BOARD_HEIGHT>>1)
9327                && (toX != fromX)
9328                && gameInfo.variant != VariantXiangqi
9329                && gameInfo.variant != VariantBerolina
9330                && (board[fromY][fromX] == WhitePawn)
9331                && (board[toY][toX] == EmptySquare)) {
9332         board[fromY][fromX] = EmptySquare;
9333         board[toY][toX] = WhitePawn;
9334         captured = board[toY - 1][toX];
9335         board[toY - 1][toX] = EmptySquare;
9336     } else if ((fromY == BOARD_HEIGHT-4)
9337                && (toX == fromX)
9338                && gameInfo.variant == VariantBerolina
9339                && (board[fromY][fromX] == WhitePawn)
9340                && (board[toY][toX] == EmptySquare)) {
9341         board[fromY][fromX] = EmptySquare;
9342         board[toY][toX] = WhitePawn;
9343         if(oldEP & EP_BEROLIN_A) {
9344                 captured = board[fromY][fromX-1];
9345                 board[fromY][fromX-1] = EmptySquare;
9346         }else{  captured = board[fromY][fromX+1];
9347                 board[fromY][fromX+1] = EmptySquare;
9348         }
9349     } else if (board[fromY][fromX] == king
9350         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9351                && toY == fromY && toX > fromX+1) {
9352         board[fromY][fromX] = EmptySquare;
9353         board[toY][toX] = king;
9354         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9355         board[fromY][BOARD_RGHT-1] = EmptySquare;
9356     } else if (board[fromY][fromX] == king
9357         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9358                && toY == fromY && toX < fromX-1) {
9359         board[fromY][fromX] = EmptySquare;
9360         board[toY][toX] = king;
9361         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9362         board[fromY][BOARD_LEFT] = EmptySquare;
9363     } else if (fromY == 7 && fromX == 3
9364                && board[fromY][fromX] == BlackKing
9365                && toY == 7 && toX == 5) {
9366         board[fromY][fromX] = EmptySquare;
9367         board[toY][toX] = BlackKing;
9368         board[fromY][7] = EmptySquare;
9369         board[toY][4] = BlackRook;
9370     } else if (fromY == 7 && fromX == 3
9371                && board[fromY][fromX] == BlackKing
9372                && toY == 7 && toX == 1) {
9373         board[fromY][fromX] = EmptySquare;
9374         board[toY][toX] = BlackKing;
9375         board[fromY][0] = EmptySquare;
9376         board[toY][2] = BlackRook;
9377     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9378                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9379                && toY < promoRank && promoChar
9380                ) {
9381         /* black pawn promotion */
9382         board[toY][toX] = CharToPiece(ToLower(promoChar));
9383         if(gameInfo.variant==VariantBughouse ||
9384            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9385             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9386         board[fromY][fromX] = EmptySquare;
9387     } else if ((fromY < BOARD_HEIGHT>>1)
9388                && (toX != fromX)
9389                && gameInfo.variant != VariantXiangqi
9390                && gameInfo.variant != VariantBerolina
9391                && (board[fromY][fromX] == BlackPawn)
9392                && (board[toY][toX] == EmptySquare)) {
9393         board[fromY][fromX] = EmptySquare;
9394         board[toY][toX] = BlackPawn;
9395         captured = board[toY + 1][toX];
9396         board[toY + 1][toX] = EmptySquare;
9397     } else if ((fromY == 3)
9398                && (toX == fromX)
9399                && gameInfo.variant == VariantBerolina
9400                && (board[fromY][fromX] == BlackPawn)
9401                && (board[toY][toX] == EmptySquare)) {
9402         board[fromY][fromX] = EmptySquare;
9403         board[toY][toX] = BlackPawn;
9404         if(oldEP & EP_BEROLIN_A) {
9405                 captured = board[fromY][fromX-1];
9406                 board[fromY][fromX-1] = EmptySquare;
9407         }else{  captured = board[fromY][fromX+1];
9408                 board[fromY][fromX+1] = EmptySquare;
9409         }
9410     } else {
9411         board[toY][toX] = board[fromY][fromX];
9412         board[fromY][fromX] = EmptySquare;
9413     }
9414   }
9415
9416     if (gameInfo.holdingsWidth != 0) {
9417
9418       /* !!A lot more code needs to be written to support holdings  */
9419       /* [HGM] OK, so I have written it. Holdings are stored in the */
9420       /* penultimate board files, so they are automaticlly stored   */
9421       /* in the game history.                                       */
9422       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9423                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9424         /* Delete from holdings, by decreasing count */
9425         /* and erasing image if necessary            */
9426         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9427         if(p < (int) BlackPawn) { /* white drop */
9428              p -= (int)WhitePawn;
9429                  p = PieceToNumber((ChessSquare)p);
9430              if(p >= gameInfo.holdingsSize) p = 0;
9431              if(--board[p][BOARD_WIDTH-2] <= 0)
9432                   board[p][BOARD_WIDTH-1] = EmptySquare;
9433              if((int)board[p][BOARD_WIDTH-2] < 0)
9434                         board[p][BOARD_WIDTH-2] = 0;
9435         } else {                  /* black drop */
9436              p -= (int)BlackPawn;
9437                  p = PieceToNumber((ChessSquare)p);
9438              if(p >= gameInfo.holdingsSize) p = 0;
9439              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9440                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9441              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9442                         board[BOARD_HEIGHT-1-p][1] = 0;
9443         }
9444       }
9445       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9446           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9447         /* [HGM] holdings: Add to holdings, if holdings exist */
9448         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9449                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9450                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9451         }
9452         p = (int) captured;
9453         if (p >= (int) BlackPawn) {
9454           p -= (int)BlackPawn;
9455           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9456                   /* in Shogi restore piece to its original  first */
9457                   captured = (ChessSquare) (DEMOTED captured);
9458                   p = DEMOTED p;
9459           }
9460           p = PieceToNumber((ChessSquare)p);
9461           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9462           board[p][BOARD_WIDTH-2]++;
9463           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9464         } else {
9465           p -= (int)WhitePawn;
9466           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9467                   captured = (ChessSquare) (DEMOTED captured);
9468                   p = DEMOTED p;
9469           }
9470           p = PieceToNumber((ChessSquare)p);
9471           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9472           board[BOARD_HEIGHT-1-p][1]++;
9473           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9474         }
9475       }
9476     } else if (gameInfo.variant == VariantAtomic) {
9477       if (captured != EmptySquare) {
9478         int y, x;
9479         for (y = toY-1; y <= toY+1; y++) {
9480           for (x = toX-1; x <= toX+1; x++) {
9481             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9482                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9483               board[y][x] = EmptySquare;
9484             }
9485           }
9486         }
9487         board[toY][toX] = EmptySquare;
9488       }
9489     }
9490     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9491         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9492     } else
9493     if(promoChar == '+') {
9494         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9495         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9496     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9497         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9498         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9499            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9500         board[toY][toX] = newPiece;
9501     }
9502     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9503                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9504         // [HGM] superchess: take promotion piece out of holdings
9505         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9506         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9507             if(!--board[k][BOARD_WIDTH-2])
9508                 board[k][BOARD_WIDTH-1] = EmptySquare;
9509         } else {
9510             if(!--board[BOARD_HEIGHT-1-k][1])
9511                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9512         }
9513     }
9514
9515 }
9516
9517 /* Updates forwardMostMove */
9518 void
9519 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9520 {
9521 //    forwardMostMove++; // [HGM] bare: moved downstream
9522
9523     (void) CoordsToAlgebraic(boards[forwardMostMove],
9524                              PosFlags(forwardMostMove),
9525                              fromY, fromX, toY, toX, promoChar,
9526                              parseList[forwardMostMove]);
9527
9528     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9529         int timeLeft; static int lastLoadFlag=0; int king, piece;
9530         piece = boards[forwardMostMove][fromY][fromX];
9531         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9532         if(gameInfo.variant == VariantKnightmate)
9533             king += (int) WhiteUnicorn - (int) WhiteKing;
9534         if(forwardMostMove == 0) {
9535             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9536                 fprintf(serverMoves, "%s;", UserName());
9537             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9538                 fprintf(serverMoves, "%s;", second.tidy);
9539             fprintf(serverMoves, "%s;", first.tidy);
9540             if(gameMode == MachinePlaysWhite)
9541                 fprintf(serverMoves, "%s;", UserName());
9542             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9543                 fprintf(serverMoves, "%s;", second.tidy);
9544         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9545         lastLoadFlag = loadFlag;
9546         // print base move
9547         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9548         // print castling suffix
9549         if( toY == fromY && piece == king ) {
9550             if(toX-fromX > 1)
9551                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9552             if(fromX-toX >1)
9553                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9554         }
9555         // e.p. suffix
9556         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9557              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9558              boards[forwardMostMove][toY][toX] == EmptySquare
9559              && fromX != toX && fromY != toY)
9560                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9561         // promotion suffix
9562         if(promoChar != NULLCHAR)
9563                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9564         if(!loadFlag) {
9565                 char buf[MOVE_LEN*2], *p; int len;
9566             fprintf(serverMoves, "/%d/%d",
9567                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9568             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9569             else                      timeLeft = blackTimeRemaining/1000;
9570             fprintf(serverMoves, "/%d", timeLeft);
9571                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9572                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9573                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9574             fprintf(serverMoves, "/%s", buf);
9575         }
9576         fflush(serverMoves);
9577     }
9578
9579     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9580         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9581       return;
9582     }
9583     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9584     if (commentList[forwardMostMove+1] != NULL) {
9585         free(commentList[forwardMostMove+1]);
9586         commentList[forwardMostMove+1] = NULL;
9587     }
9588     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9589     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9590     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9591     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9592     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9593     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9594     adjustedClock = FALSE;
9595     gameInfo.result = GameUnfinished;
9596     if (gameInfo.resultDetails != NULL) {
9597         free(gameInfo.resultDetails);
9598         gameInfo.resultDetails = NULL;
9599     }
9600     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9601                               moveList[forwardMostMove - 1]);
9602     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9603       case MT_NONE:
9604       case MT_STALEMATE:
9605       default:
9606         break;
9607       case MT_CHECK:
9608         if(gameInfo.variant != VariantShogi)
9609             strcat(parseList[forwardMostMove - 1], "+");
9610         break;
9611       case MT_CHECKMATE:
9612       case MT_STAINMATE:
9613         strcat(parseList[forwardMostMove - 1], "#");
9614         break;
9615     }
9616
9617 }
9618
9619 /* Updates currentMove if not pausing */
9620 void
9621 ShowMove (int fromX, int fromY, int toX, int toY)
9622 {
9623     int instant = (gameMode == PlayFromGameFile) ?
9624         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9625     if(appData.noGUI) return;
9626     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9627         if (!instant) {
9628             if (forwardMostMove == currentMove + 1) {
9629                 AnimateMove(boards[forwardMostMove - 1],
9630                             fromX, fromY, toX, toY);
9631             }
9632             if (appData.highlightLastMove) {
9633                 SetHighlights(fromX, fromY, toX, toY);
9634             }
9635         }
9636         currentMove = forwardMostMove;
9637     }
9638
9639     if (instant) return;
9640
9641     DisplayMove(currentMove - 1);
9642     DrawPosition(FALSE, boards[currentMove]);
9643     DisplayBothClocks();
9644     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9645 }
9646
9647 void
9648 SendEgtPath (ChessProgramState *cps)
9649 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9650         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9651
9652         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9653
9654         while(*p) {
9655             char c, *q = name+1, *r, *s;
9656
9657             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9658             while(*p && *p != ',') *q++ = *p++;
9659             *q++ = ':'; *q = 0;
9660             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9661                 strcmp(name, ",nalimov:") == 0 ) {
9662                 // take nalimov path from the menu-changeable option first, if it is defined
9663               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9664                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9665             } else
9666             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9667                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9668                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9669                 s = r = StrStr(s, ":") + 1; // beginning of path info
9670                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9671                 c = *r; *r = 0;             // temporarily null-terminate path info
9672                     *--q = 0;               // strip of trailig ':' from name
9673                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9674                 *r = c;
9675                 SendToProgram(buf,cps);     // send egtbpath command for this format
9676             }
9677             if(*p == ',') p++; // read away comma to position for next format name
9678         }
9679 }
9680
9681 void
9682 InitChessProgram (ChessProgramState *cps, int setup)
9683 /* setup needed to setup FRC opening position */
9684 {
9685     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9686     if (appData.noChessProgram) return;
9687     hintRequested = FALSE;
9688     bookRequested = FALSE;
9689
9690     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9691     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9692     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9693     if(cps->memSize) { /* [HGM] memory */
9694       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9695         SendToProgram(buf, cps);
9696     }
9697     SendEgtPath(cps); /* [HGM] EGT */
9698     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9699       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9700         SendToProgram(buf, cps);
9701     }
9702
9703     SendToProgram(cps->initString, cps);
9704     if (gameInfo.variant != VariantNormal &&
9705         gameInfo.variant != VariantLoadable
9706         /* [HGM] also send variant if board size non-standard */
9707         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9708                                             ) {
9709       char *v = VariantName(gameInfo.variant);
9710       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9711         /* [HGM] in protocol 1 we have to assume all variants valid */
9712         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9713         DisplayFatalError(buf, 0, 1);
9714         return;
9715       }
9716
9717       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9718       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9719       if( gameInfo.variant == VariantXiangqi )
9720            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9721       if( gameInfo.variant == VariantShogi )
9722            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9723       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9724            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9725       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9726           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9727            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9728       if( gameInfo.variant == VariantCourier )
9729            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9730       if( gameInfo.variant == VariantSuper )
9731            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9732       if( gameInfo.variant == VariantGreat )
9733            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9734       if( gameInfo.variant == VariantSChess )
9735            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9736       if( gameInfo.variant == VariantGrand )
9737            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9738
9739       if(overruled) {
9740         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9741                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9742            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9743            if(StrStr(cps->variants, b) == NULL) {
9744                // specific sized variant not known, check if general sizing allowed
9745                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9746                    if(StrStr(cps->variants, "boardsize") == NULL) {
9747                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9748                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9749                        DisplayFatalError(buf, 0, 1);
9750                        return;
9751                    }
9752                    /* [HGM] here we really should compare with the maximum supported board size */
9753                }
9754            }
9755       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9756       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9757       SendToProgram(buf, cps);
9758     }
9759     currentlyInitializedVariant = gameInfo.variant;
9760
9761     /* [HGM] send opening position in FRC to first engine */
9762     if(setup) {
9763           SendToProgram("force\n", cps);
9764           SendBoard(cps, 0);
9765           /* engine is now in force mode! Set flag to wake it up after first move. */
9766           setboardSpoiledMachineBlack = 1;
9767     }
9768
9769     if (cps->sendICS) {
9770       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9771       SendToProgram(buf, cps);
9772     }
9773     cps->maybeThinking = FALSE;
9774     cps->offeredDraw = 0;
9775     if (!appData.icsActive) {
9776         SendTimeControl(cps, movesPerSession, timeControl,
9777                         timeIncrement, appData.searchDepth,
9778                         searchTime);
9779     }
9780     if (appData.showThinking
9781         // [HGM] thinking: four options require thinking output to be sent
9782         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9783                                 ) {
9784         SendToProgram("post\n", cps);
9785     }
9786     SendToProgram("hard\n", cps);
9787     if (!appData.ponderNextMove) {
9788         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9789            it without being sure what state we are in first.  "hard"
9790            is not a toggle, so that one is OK.
9791          */
9792         SendToProgram("easy\n", cps);
9793     }
9794     if (cps->usePing) {
9795       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9796       SendToProgram(buf, cps);
9797     }
9798     cps->initDone = TRUE;
9799     ClearEngineOutputPane(cps == &second);
9800 }
9801
9802
9803 void
9804 StartChessProgram (ChessProgramState *cps)
9805 {
9806     char buf[MSG_SIZ];
9807     int err;
9808
9809     if (appData.noChessProgram) return;
9810     cps->initDone = FALSE;
9811
9812     if (strcmp(cps->host, "localhost") == 0) {
9813         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9814     } else if (*appData.remoteShell == NULLCHAR) {
9815         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9816     } else {
9817         if (*appData.remoteUser == NULLCHAR) {
9818           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9819                     cps->program);
9820         } else {
9821           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9822                     cps->host, appData.remoteUser, cps->program);
9823         }
9824         err = StartChildProcess(buf, "", &cps->pr);
9825     }
9826
9827     if (err != 0) {
9828       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9829         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9830         if(cps != &first) return;
9831         appData.noChessProgram = TRUE;
9832         ThawUI();
9833         SetNCPMode();
9834 //      DisplayFatalError(buf, err, 1);
9835 //      cps->pr = NoProc;
9836 //      cps->isr = NULL;
9837         return;
9838     }
9839
9840     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9841     if (cps->protocolVersion > 1) {
9842       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9843       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9844       cps->comboCnt = 0;  //                and values of combo boxes
9845       SendToProgram(buf, cps);
9846     } else {
9847       SendToProgram("xboard\n", cps);
9848     }
9849 }
9850
9851 void
9852 TwoMachinesEventIfReady P((void))
9853 {
9854   static int curMess = 0;
9855   if (first.lastPing != first.lastPong) {
9856     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9857     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9858     return;
9859   }
9860   if (second.lastPing != second.lastPong) {
9861     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9862     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9863     return;
9864   }
9865   DisplayMessage("", ""); curMess = 0;
9866   ThawUI();
9867   TwoMachinesEvent();
9868 }
9869
9870 char *
9871 MakeName (char *template)
9872 {
9873     time_t clock;
9874     struct tm *tm;
9875     static char buf[MSG_SIZ];
9876     char *p = buf;
9877     int i;
9878
9879     clock = time((time_t *)NULL);
9880     tm = localtime(&clock);
9881
9882     while(*p++ = *template++) if(p[-1] == '%') {
9883         switch(*template++) {
9884           case 0:   *p = 0; return buf;
9885           case 'Y': i = tm->tm_year+1900; break;
9886           case 'y': i = tm->tm_year-100; break;
9887           case 'M': i = tm->tm_mon+1; break;
9888           case 'd': i = tm->tm_mday; break;
9889           case 'h': i = tm->tm_hour; break;
9890           case 'm': i = tm->tm_min; break;
9891           case 's': i = tm->tm_sec; break;
9892           default:  i = 0;
9893         }
9894         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9895     }
9896     return buf;
9897 }
9898
9899 int
9900 CountPlayers (char *p)
9901 {
9902     int n = 0;
9903     while(p = strchr(p, '\n')) p++, n++; // count participants
9904     return n;
9905 }
9906
9907 FILE *
9908 WriteTourneyFile (char *results, FILE *f)
9909 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9910     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9911     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9912         // create a file with tournament description
9913         fprintf(f, "-participants {%s}\n", appData.participants);
9914         fprintf(f, "-seedBase %d\n", appData.seedBase);
9915         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9916         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9917         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9918         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9919         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9920         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9921         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9922         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9923         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9924         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9925         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9926         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9927         if(searchTime > 0)
9928                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9929         else {
9930                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9931                 fprintf(f, "-tc %s\n", appData.timeControl);
9932                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9933         }
9934         fprintf(f, "-results \"%s\"\n", results);
9935     }
9936     return f;
9937 }
9938
9939 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9940
9941 void
9942 Substitute (char *participants, int expunge)
9943 {
9944     int i, changed, changes=0, nPlayers=0;
9945     char *p, *q, *r, buf[MSG_SIZ];
9946     if(participants == NULL) return;
9947     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9948     r = p = participants; q = appData.participants;
9949     while(*p && *p == *q) {
9950         if(*p == '\n') r = p+1, nPlayers++;
9951         p++; q++;
9952     }
9953     if(*p) { // difference
9954         while(*p && *p++ != '\n');
9955         while(*q && *q++ != '\n');
9956       changed = nPlayers;
9957         changes = 1 + (strcmp(p, q) != 0);
9958     }
9959     if(changes == 1) { // a single engine mnemonic was changed
9960         q = r; while(*q) nPlayers += (*q++ == '\n');
9961         p = buf; while(*r && (*p = *r++) != '\n') p++;
9962         *p = NULLCHAR;
9963         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9964         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9965         if(mnemonic[i]) { // The substitute is valid
9966             FILE *f;
9967             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9968                 flock(fileno(f), LOCK_EX);
9969                 ParseArgsFromFile(f);
9970                 fseek(f, 0, SEEK_SET);
9971                 FREE(appData.participants); appData.participants = participants;
9972                 if(expunge) { // erase results of replaced engine
9973                     int len = strlen(appData.results), w, b, dummy;
9974                     for(i=0; i<len; i++) {
9975                         Pairing(i, nPlayers, &w, &b, &dummy);
9976                         if((w == changed || b == changed) && appData.results[i] == '*') {
9977                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9978                             fclose(f);
9979                             return;
9980                         }
9981                     }
9982                     for(i=0; i<len; i++) {
9983                         Pairing(i, nPlayers, &w, &b, &dummy);
9984                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9985                     }
9986                 }
9987                 WriteTourneyFile(appData.results, f);
9988                 fclose(f); // release lock
9989                 return;
9990             }
9991         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9992     }
9993     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9994     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9995     free(participants);
9996     return;
9997 }
9998
9999 int
10000 CreateTourney (char *name)
10001 {
10002         FILE *f;
10003         if(matchMode && strcmp(name, appData.tourneyFile)) {
10004              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10005         }
10006         if(name[0] == NULLCHAR) {
10007             if(appData.participants[0])
10008                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10009             return 0;
10010         }
10011         f = fopen(name, "r");
10012         if(f) { // file exists
10013             ASSIGN(appData.tourneyFile, name);
10014             ParseArgsFromFile(f); // parse it
10015         } else {
10016             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10017             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10018                 DisplayError(_("Not enough participants"), 0);
10019                 return 0;
10020             }
10021             ASSIGN(appData.tourneyFile, name);
10022             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10023             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10024         }
10025         fclose(f);
10026         appData.noChessProgram = FALSE;
10027         appData.clockMode = TRUE;
10028         SetGNUMode();
10029         return 1;
10030 }
10031
10032 int
10033 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10034 {
10035     char buf[MSG_SIZ], *p, *q;
10036     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10037     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10038     skip = !all && group[0]; // if group requested, we start in skip mode
10039     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10040         p = names; q = buf; header = 0;
10041         while(*p && *p != '\n') *q++ = *p++;
10042         *q = 0;
10043         if(*p == '\n') p++;
10044         if(buf[0] == '#') {
10045             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10046             depth++; // we must be entering a new group
10047             if(all) continue; // suppress printing group headers when complete list requested
10048             header = 1;
10049             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10050         }
10051         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10052         if(engineList[i]) free(engineList[i]);
10053         engineList[i] = strdup(buf);
10054         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10055         if(engineMnemonic[i]) free(engineMnemonic[i]);
10056         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10057             strcat(buf, " (");
10058             sscanf(q + 8, "%s", buf + strlen(buf));
10059             strcat(buf, ")");
10060         }
10061         engineMnemonic[i] = strdup(buf);
10062         i++;
10063     }
10064     engineList[i] = engineMnemonic[i] = NULL;
10065     return i;
10066 }
10067
10068 // following implemented as macro to avoid type limitations
10069 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10070
10071 void
10072 SwapEngines (int n)
10073 {   // swap settings for first engine and other engine (so far only some selected options)
10074     int h;
10075     char *p;
10076     if(n == 0) return;
10077     SWAP(directory, p)
10078     SWAP(chessProgram, p)
10079     SWAP(isUCI, h)
10080     SWAP(hasOwnBookUCI, h)
10081     SWAP(protocolVersion, h)
10082     SWAP(reuse, h)
10083     SWAP(scoreIsAbsolute, h)
10084     SWAP(timeOdds, h)
10085     SWAP(logo, p)
10086     SWAP(pgnName, p)
10087     SWAP(pvSAN, h)
10088     SWAP(engOptions, p)
10089     SWAP(engInitString, p)
10090     SWAP(computerString, p)
10091     SWAP(features, p)
10092     SWAP(fenOverride, p)
10093     SWAP(NPS, h)
10094     SWAP(accumulateTC, h)
10095     SWAP(host, p)
10096 }
10097
10098 int
10099 SetPlayer (int player, char *p)
10100 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10101     int i;
10102     char buf[MSG_SIZ], *engineName;
10103     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10104     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10105     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10106     if(mnemonic[i]) {
10107         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10108         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10109         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10110         ParseArgsFromString(buf);
10111     }
10112     free(engineName);
10113     return i;
10114 }
10115
10116 char *recentEngines;
10117
10118 void
10119 RecentEngineEvent (int nr)
10120 {
10121     int n;
10122 //    SwapEngines(1); // bump first to second
10123 //    ReplaceEngine(&second, 1); // and load it there
10124     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10125     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10126     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10127         ReplaceEngine(&first, 0);
10128         FloatToFront(&appData.recentEngineList, command[n]);
10129     }
10130 }
10131
10132 int
10133 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10134 {   // determine players from game number
10135     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10136
10137     if(appData.tourneyType == 0) {
10138         roundsPerCycle = (nPlayers - 1) | 1;
10139         pairingsPerRound = nPlayers / 2;
10140     } else if(appData.tourneyType > 0) {
10141         roundsPerCycle = nPlayers - appData.tourneyType;
10142         pairingsPerRound = appData.tourneyType;
10143     }
10144     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10145     gamesPerCycle = gamesPerRound * roundsPerCycle;
10146     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10147     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10148     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10149     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10150     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10151     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10152
10153     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10154     if(appData.roundSync) *syncInterval = gamesPerRound;
10155
10156     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10157
10158     if(appData.tourneyType == 0) {
10159         if(curPairing == (nPlayers-1)/2 ) {
10160             *whitePlayer = curRound;
10161             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10162         } else {
10163             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10164             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10165             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10166             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10167         }
10168     } else if(appData.tourneyType > 1) {
10169         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10170         *whitePlayer = curRound + appData.tourneyType;
10171     } else if(appData.tourneyType > 0) {
10172         *whitePlayer = curPairing;
10173         *blackPlayer = curRound + appData.tourneyType;
10174     }
10175
10176     // take care of white/black alternation per round. 
10177     // For cycles and games this is already taken care of by default, derived from matchGame!
10178     return curRound & 1;
10179 }
10180
10181 int
10182 NextTourneyGame (int nr, int *swapColors)
10183 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10184     char *p, *q;
10185     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10186     FILE *tf;
10187     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10188     tf = fopen(appData.tourneyFile, "r");
10189     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10190     ParseArgsFromFile(tf); fclose(tf);
10191     InitTimeControls(); // TC might be altered from tourney file
10192
10193     nPlayers = CountPlayers(appData.participants); // count participants
10194     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10195     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10196
10197     if(syncInterval) {
10198         p = q = appData.results;
10199         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10200         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10201             DisplayMessage(_("Waiting for other game(s)"),"");
10202             waitingForGame = TRUE;
10203             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10204             return 0;
10205         }
10206         waitingForGame = FALSE;
10207     }
10208
10209     if(appData.tourneyType < 0) {
10210         if(nr>=0 && !pairingReceived) {
10211             char buf[1<<16];
10212             if(pairing.pr == NoProc) {
10213                 if(!appData.pairingEngine[0]) {
10214                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10215                     return 0;
10216                 }
10217                 StartChessProgram(&pairing); // starts the pairing engine
10218             }
10219             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10220             SendToProgram(buf, &pairing);
10221             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10222             SendToProgram(buf, &pairing);
10223             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10224         }
10225         pairingReceived = 0;                              // ... so we continue here 
10226         *swapColors = 0;
10227         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10228         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10229         matchGame = 1; roundNr = nr / syncInterval + 1;
10230     }
10231
10232     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10233
10234     // redefine engines, engine dir, etc.
10235     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10236     if(first.pr == NoProc) {
10237       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10238       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10239     }
10240     if(second.pr == NoProc) {
10241       SwapEngines(1);
10242       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10243       SwapEngines(1);         // and make that valid for second engine by swapping
10244       InitEngine(&second, 1);
10245     }
10246     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10247     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10248     return 1;
10249 }
10250
10251 void
10252 NextMatchGame ()
10253 {   // performs game initialization that does not invoke engines, and then tries to start the game
10254     int res, firstWhite, swapColors = 0;
10255     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10256     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
10257         char buf[MSG_SIZ];
10258         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10259         if(strcmp(buf, currentDebugFile)) { // name has changed
10260             FILE *f = fopen(buf, "w");
10261             if(f) { // if opening the new file failed, just keep using the old one
10262                 ASSIGN(currentDebugFile, buf);
10263                 fclose(debugFP);
10264                 debugFP = f;
10265             }
10266             if(appData.serverFileName) {
10267                 if(serverFP) fclose(serverFP);
10268                 serverFP = fopen(appData.serverFileName, "w");
10269                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10270                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10271             }
10272         }
10273     }
10274     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10275     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10276     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10277     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10278     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10279     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10280     Reset(FALSE, first.pr != NoProc);
10281     res = LoadGameOrPosition(matchGame); // setup game
10282     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10283     if(!res) return; // abort when bad game/pos file
10284     TwoMachinesEvent();
10285 }
10286
10287 void
10288 UserAdjudicationEvent (int result)
10289 {
10290     ChessMove gameResult = GameIsDrawn;
10291
10292     if( result > 0 ) {
10293         gameResult = WhiteWins;
10294     }
10295     else if( result < 0 ) {
10296         gameResult = BlackWins;
10297     }
10298
10299     if( gameMode == TwoMachinesPlay ) {
10300         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10301     }
10302 }
10303
10304
10305 // [HGM] save: calculate checksum of game to make games easily identifiable
10306 int
10307 StringCheckSum (char *s)
10308 {
10309         int i = 0;
10310         if(s==NULL) return 0;
10311         while(*s) i = i*259 + *s++;
10312         return i;
10313 }
10314
10315 int
10316 GameCheckSum ()
10317 {
10318         int i, sum=0;
10319         for(i=backwardMostMove; i<forwardMostMove; i++) {
10320                 sum += pvInfoList[i].depth;
10321                 sum += StringCheckSum(parseList[i]);
10322                 sum += StringCheckSum(commentList[i]);
10323                 sum *= 261;
10324         }
10325         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10326         return sum + StringCheckSum(commentList[i]);
10327 } // end of save patch
10328
10329 void
10330 GameEnds (ChessMove result, char *resultDetails, int whosays)
10331 {
10332     GameMode nextGameMode;
10333     int isIcsGame;
10334     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10335
10336     if(endingGame) return; /* [HGM] crash: forbid recursion */
10337     endingGame = 1;
10338     if(twoBoards) { // [HGM] dual: switch back to one board
10339         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10340         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10341     }
10342     if (appData.debugMode) {
10343       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10344               result, resultDetails ? resultDetails : "(null)", whosays);
10345     }
10346
10347     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10348
10349     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10350         /* If we are playing on ICS, the server decides when the
10351            game is over, but the engine can offer to draw, claim
10352            a draw, or resign.
10353          */
10354 #if ZIPPY
10355         if (appData.zippyPlay && first.initDone) {
10356             if (result == GameIsDrawn) {
10357                 /* In case draw still needs to be claimed */
10358                 SendToICS(ics_prefix);
10359                 SendToICS("draw\n");
10360             } else if (StrCaseStr(resultDetails, "resign")) {
10361                 SendToICS(ics_prefix);
10362                 SendToICS("resign\n");
10363             }
10364         }
10365 #endif
10366         endingGame = 0; /* [HGM] crash */
10367         return;
10368     }
10369
10370     /* If we're loading the game from a file, stop */
10371     if (whosays == GE_FILE) {
10372       (void) StopLoadGameTimer();
10373       gameFileFP = NULL;
10374     }
10375
10376     /* Cancel draw offers */
10377     first.offeredDraw = second.offeredDraw = 0;
10378
10379     /* If this is an ICS game, only ICS can really say it's done;
10380        if not, anyone can. */
10381     isIcsGame = (gameMode == IcsPlayingWhite ||
10382                  gameMode == IcsPlayingBlack ||
10383                  gameMode == IcsObserving    ||
10384                  gameMode == IcsExamining);
10385
10386     if (!isIcsGame || whosays == GE_ICS) {
10387         /* OK -- not an ICS game, or ICS said it was done */
10388         StopClocks();
10389         if (!isIcsGame && !appData.noChessProgram)
10390           SetUserThinkingEnables();
10391
10392         /* [HGM] if a machine claims the game end we verify this claim */
10393         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10394             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10395                 char claimer;
10396                 ChessMove trueResult = (ChessMove) -1;
10397
10398                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10399                                             first.twoMachinesColor[0] :
10400                                             second.twoMachinesColor[0] ;
10401
10402                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10403                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10404                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10405                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10406                 } else
10407                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10408                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10409                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10410                 } else
10411                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10412                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10413                 }
10414
10415                 // now verify win claims, but not in drop games, as we don't understand those yet
10416                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10417                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10418                     (result == WhiteWins && claimer == 'w' ||
10419                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10420                       if (appData.debugMode) {
10421                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10422                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10423                       }
10424                       if(result != trueResult) {
10425                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10426                               result = claimer == 'w' ? BlackWins : WhiteWins;
10427                               resultDetails = buf;
10428                       }
10429                 } else
10430                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10431                     && (forwardMostMove <= backwardMostMove ||
10432                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10433                         (claimer=='b')==(forwardMostMove&1))
10434                                                                                   ) {
10435                       /* [HGM] verify: draws that were not flagged are false claims */
10436                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10437                       result = claimer == 'w' ? BlackWins : WhiteWins;
10438                       resultDetails = buf;
10439                 }
10440                 /* (Claiming a loss is accepted no questions asked!) */
10441             }
10442             /* [HGM] bare: don't allow bare King to win */
10443             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10444                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10445                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10446                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10447                && result != GameIsDrawn)
10448             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10449                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10450                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10451                         if(p >= 0 && p <= (int)WhiteKing) k++;
10452                 }
10453                 if (appData.debugMode) {
10454                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10455                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10456                 }
10457                 if(k <= 1) {
10458                         result = GameIsDrawn;
10459                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10460                         resultDetails = buf;
10461                 }
10462             }
10463         }
10464
10465
10466         if(serverMoves != NULL && !loadFlag) { char c = '=';
10467             if(result==WhiteWins) c = '+';
10468             if(result==BlackWins) c = '-';
10469             if(resultDetails != NULL)
10470                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10471         }
10472         if (resultDetails != NULL) {
10473             gameInfo.result = result;
10474             gameInfo.resultDetails = StrSave(resultDetails);
10475
10476             /* display last move only if game was not loaded from file */
10477             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10478                 DisplayMove(currentMove - 1);
10479
10480             if (forwardMostMove != 0) {
10481                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10482                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10483                                                                 ) {
10484                     if (*appData.saveGameFile != NULLCHAR) {
10485                         SaveGameToFile(appData.saveGameFile, TRUE);
10486                     } else if (appData.autoSaveGames) {
10487                         AutoSaveGame();
10488                     }
10489                     if (*appData.savePositionFile != NULLCHAR) {
10490                         SavePositionToFile(appData.savePositionFile);
10491                     }
10492                 }
10493             }
10494
10495             /* Tell program how game ended in case it is learning */
10496             /* [HGM] Moved this to after saving the PGN, just in case */
10497             /* engine died and we got here through time loss. In that */
10498             /* case we will get a fatal error writing the pipe, which */
10499             /* would otherwise lose us the PGN.                       */
10500             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10501             /* output during GameEnds should never be fatal anymore   */
10502             if (gameMode == MachinePlaysWhite ||
10503                 gameMode == MachinePlaysBlack ||
10504                 gameMode == TwoMachinesPlay ||
10505                 gameMode == IcsPlayingWhite ||
10506                 gameMode == IcsPlayingBlack ||
10507                 gameMode == BeginningOfGame) {
10508                 char buf[MSG_SIZ];
10509                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10510                         resultDetails);
10511                 if (first.pr != NoProc) {
10512                     SendToProgram(buf, &first);
10513                 }
10514                 if (second.pr != NoProc &&
10515                     gameMode == TwoMachinesPlay) {
10516                     SendToProgram(buf, &second);
10517                 }
10518             }
10519         }
10520
10521         if (appData.icsActive) {
10522             if (appData.quietPlay &&
10523                 (gameMode == IcsPlayingWhite ||
10524                  gameMode == IcsPlayingBlack)) {
10525                 SendToICS(ics_prefix);
10526                 SendToICS("set shout 1\n");
10527             }
10528             nextGameMode = IcsIdle;
10529             ics_user_moved = FALSE;
10530             /* clean up premove.  It's ugly when the game has ended and the
10531              * premove highlights are still on the board.
10532              */
10533             if (gotPremove) {
10534               gotPremove = FALSE;
10535               ClearPremoveHighlights();
10536               DrawPosition(FALSE, boards[currentMove]);
10537             }
10538             if (whosays == GE_ICS) {
10539                 switch (result) {
10540                 case WhiteWins:
10541                     if (gameMode == IcsPlayingWhite)
10542                         PlayIcsWinSound();
10543                     else if(gameMode == IcsPlayingBlack)
10544                         PlayIcsLossSound();
10545                     break;
10546                 case BlackWins:
10547                     if (gameMode == IcsPlayingBlack)
10548                         PlayIcsWinSound();
10549                     else if(gameMode == IcsPlayingWhite)
10550                         PlayIcsLossSound();
10551                     break;
10552                 case GameIsDrawn:
10553                     PlayIcsDrawSound();
10554                     break;
10555                 default:
10556                     PlayIcsUnfinishedSound();
10557                 }
10558             }
10559         } else if (gameMode == EditGame ||
10560                    gameMode == PlayFromGameFile ||
10561                    gameMode == AnalyzeMode ||
10562                    gameMode == AnalyzeFile) {
10563             nextGameMode = gameMode;
10564         } else {
10565             nextGameMode = EndOfGame;
10566         }
10567         pausing = FALSE;
10568         ModeHighlight();
10569     } else {
10570         nextGameMode = gameMode;
10571     }
10572
10573     if (appData.noChessProgram) {
10574         gameMode = nextGameMode;
10575         ModeHighlight();
10576         endingGame = 0; /* [HGM] crash */
10577         return;
10578     }
10579
10580     if (first.reuse) {
10581         /* Put first chess program into idle state */
10582         if (first.pr != NoProc &&
10583             (gameMode == MachinePlaysWhite ||
10584              gameMode == MachinePlaysBlack ||
10585              gameMode == TwoMachinesPlay ||
10586              gameMode == IcsPlayingWhite ||
10587              gameMode == IcsPlayingBlack ||
10588              gameMode == BeginningOfGame)) {
10589             SendToProgram("force\n", &first);
10590             if (first.usePing) {
10591               char buf[MSG_SIZ];
10592               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10593               SendToProgram(buf, &first);
10594             }
10595         }
10596     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10597         /* Kill off first chess program */
10598         if (first.isr != NULL)
10599           RemoveInputSource(first.isr);
10600         first.isr = NULL;
10601
10602         if (first.pr != NoProc) {
10603             ExitAnalyzeMode();
10604             DoSleep( appData.delayBeforeQuit );
10605             SendToProgram("quit\n", &first);
10606             DoSleep( appData.delayAfterQuit );
10607             DestroyChildProcess(first.pr, first.useSigterm);
10608         }
10609         first.pr = NoProc;
10610     }
10611     if (second.reuse) {
10612         /* Put second chess program into idle state */
10613         if (second.pr != NoProc &&
10614             gameMode == TwoMachinesPlay) {
10615             SendToProgram("force\n", &second);
10616             if (second.usePing) {
10617               char buf[MSG_SIZ];
10618               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10619               SendToProgram(buf, &second);
10620             }
10621         }
10622     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10623         /* Kill off second chess program */
10624         if (second.isr != NULL)
10625           RemoveInputSource(second.isr);
10626         second.isr = NULL;
10627
10628         if (second.pr != NoProc) {
10629             DoSleep( appData.delayBeforeQuit );
10630             SendToProgram("quit\n", &second);
10631             DoSleep( appData.delayAfterQuit );
10632             DestroyChildProcess(second.pr, second.useSigterm);
10633         }
10634         second.pr = NoProc;
10635     }
10636
10637     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10638         char resChar = '=';
10639         switch (result) {
10640         case WhiteWins:
10641           resChar = '+';
10642           if (first.twoMachinesColor[0] == 'w') {
10643             first.matchWins++;
10644           } else {
10645             second.matchWins++;
10646           }
10647           break;
10648         case BlackWins:
10649           resChar = '-';
10650           if (first.twoMachinesColor[0] == 'b') {
10651             first.matchWins++;
10652           } else {
10653             second.matchWins++;
10654           }
10655           break;
10656         case GameUnfinished:
10657           resChar = ' ';
10658         default:
10659           break;
10660         }
10661
10662         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10663         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10664             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10665             ReserveGame(nextGame, resChar); // sets nextGame
10666             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10667             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10668         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10669
10670         if (nextGame <= appData.matchGames && !abortMatch) {
10671             gameMode = nextGameMode;
10672             matchGame = nextGame; // this will be overruled in tourney mode!
10673             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10674             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10675             endingGame = 0; /* [HGM] crash */
10676             return;
10677         } else {
10678             gameMode = nextGameMode;
10679             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10680                      first.tidy, second.tidy,
10681                      first.matchWins, second.matchWins,
10682                      appData.matchGames - (first.matchWins + second.matchWins));
10683             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10684             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10685             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10686             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10687                 first.twoMachinesColor = "black\n";
10688                 second.twoMachinesColor = "white\n";
10689             } else {
10690                 first.twoMachinesColor = "white\n";
10691                 second.twoMachinesColor = "black\n";
10692             }
10693         }
10694     }
10695     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10696         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10697       ExitAnalyzeMode();
10698     gameMode = nextGameMode;
10699     ModeHighlight();
10700     endingGame = 0;  /* [HGM] crash */
10701     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10702         if(matchMode == TRUE) { // match through command line: exit with or without popup
10703             if(ranking) {
10704                 ToNrEvent(forwardMostMove);
10705                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10706                 else ExitEvent(0);
10707             } else DisplayFatalError(buf, 0, 0);
10708         } else { // match through menu; just stop, with or without popup
10709             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10710             ModeHighlight();
10711             if(ranking){
10712                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10713             } else DisplayNote(buf);
10714       }
10715       if(ranking) free(ranking);
10716     }
10717 }
10718
10719 /* Assumes program was just initialized (initString sent).
10720    Leaves program in force mode. */
10721 void
10722 FeedMovesToProgram (ChessProgramState *cps, int upto)
10723 {
10724     int i;
10725
10726     if (appData.debugMode)
10727       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10728               startedFromSetupPosition ? "position and " : "",
10729               backwardMostMove, upto, cps->which);
10730     if(currentlyInitializedVariant != gameInfo.variant) {
10731       char buf[MSG_SIZ];
10732         // [HGM] variantswitch: make engine aware of new variant
10733         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10734                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10735         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10736         SendToProgram(buf, cps);
10737         currentlyInitializedVariant = gameInfo.variant;
10738     }
10739     SendToProgram("force\n", cps);
10740     if (startedFromSetupPosition) {
10741         SendBoard(cps, backwardMostMove);
10742     if (appData.debugMode) {
10743         fprintf(debugFP, "feedMoves\n");
10744     }
10745     }
10746     for (i = backwardMostMove; i < upto; i++) {
10747         SendMoveToProgram(i, cps);
10748     }
10749 }
10750
10751
10752 int
10753 ResurrectChessProgram ()
10754 {
10755      /* The chess program may have exited.
10756         If so, restart it and feed it all the moves made so far. */
10757     static int doInit = 0;
10758
10759     if (appData.noChessProgram) return 1;
10760
10761     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10762         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10763         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10764         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10765     } else {
10766         if (first.pr != NoProc) return 1;
10767         StartChessProgram(&first);
10768     }
10769     InitChessProgram(&first, FALSE);
10770     FeedMovesToProgram(&first, currentMove);
10771
10772     if (!first.sendTime) {
10773         /* can't tell gnuchess what its clock should read,
10774            so we bow to its notion. */
10775         ResetClocks();
10776         timeRemaining[0][currentMove] = whiteTimeRemaining;
10777         timeRemaining[1][currentMove] = blackTimeRemaining;
10778     }
10779
10780     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10781                 appData.icsEngineAnalyze) && first.analysisSupport) {
10782       SendToProgram("analyze\n", &first);
10783       first.analyzing = TRUE;
10784     }
10785     return 1;
10786 }
10787
10788 /*
10789  * Button procedures
10790  */
10791 void
10792 Reset (int redraw, int init)
10793 {
10794     int i;
10795
10796     if (appData.debugMode) {
10797         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10798                 redraw, init, gameMode);
10799     }
10800     CleanupTail(); // [HGM] vari: delete any stored variations
10801     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10802     pausing = pauseExamInvalid = FALSE;
10803     startedFromSetupPosition = blackPlaysFirst = FALSE;
10804     firstMove = TRUE;
10805     whiteFlag = blackFlag = FALSE;
10806     userOfferedDraw = FALSE;
10807     hintRequested = bookRequested = FALSE;
10808     first.maybeThinking = FALSE;
10809     second.maybeThinking = FALSE;
10810     first.bookSuspend = FALSE; // [HGM] book
10811     second.bookSuspend = FALSE;
10812     thinkOutput[0] = NULLCHAR;
10813     lastHint[0] = NULLCHAR;
10814     ClearGameInfo(&gameInfo);
10815     gameInfo.variant = StringToVariant(appData.variant);
10816     ics_user_moved = ics_clock_paused = FALSE;
10817     ics_getting_history = H_FALSE;
10818     ics_gamenum = -1;
10819     white_holding[0] = black_holding[0] = NULLCHAR;
10820     ClearProgramStats();
10821     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10822
10823     ResetFrontEnd();
10824     ClearHighlights();
10825     flipView = appData.flipView;
10826     ClearPremoveHighlights();
10827     gotPremove = FALSE;
10828     alarmSounded = FALSE;
10829
10830     GameEnds(EndOfFile, NULL, GE_PLAYER);
10831     if(appData.serverMovesName != NULL) {
10832         /* [HGM] prepare to make moves file for broadcasting */
10833         clock_t t = clock();
10834         if(serverMoves != NULL) fclose(serverMoves);
10835         serverMoves = fopen(appData.serverMovesName, "r");
10836         if(serverMoves != NULL) {
10837             fclose(serverMoves);
10838             /* delay 15 sec before overwriting, so all clients can see end */
10839             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10840         }
10841         serverMoves = fopen(appData.serverMovesName, "w");
10842     }
10843
10844     ExitAnalyzeMode();
10845     gameMode = BeginningOfGame;
10846     ModeHighlight();
10847     if(appData.icsActive) gameInfo.variant = VariantNormal;
10848     currentMove = forwardMostMove = backwardMostMove = 0;
10849     MarkTargetSquares(1);
10850     InitPosition(redraw);
10851     for (i = 0; i < MAX_MOVES; i++) {
10852         if (commentList[i] != NULL) {
10853             free(commentList[i]);
10854             commentList[i] = NULL;
10855         }
10856     }
10857     ResetClocks();
10858     timeRemaining[0][0] = whiteTimeRemaining;
10859     timeRemaining[1][0] = blackTimeRemaining;
10860
10861     if (first.pr == NoProc) {
10862         StartChessProgram(&first);
10863     }
10864     if (init) {
10865             InitChessProgram(&first, startedFromSetupPosition);
10866     }
10867     DisplayTitle("");
10868     DisplayMessage("", "");
10869     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10870     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10871     ClearMap();        // [HGM] exclude: invalidate map
10872 }
10873
10874 void
10875 AutoPlayGameLoop ()
10876 {
10877     for (;;) {
10878         if (!AutoPlayOneMove())
10879           return;
10880         if (matchMode || appData.timeDelay == 0)
10881           continue;
10882         if (appData.timeDelay < 0)
10883           return;
10884         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10885         break;
10886     }
10887 }
10888
10889
10890 int
10891 AutoPlayOneMove ()
10892 {
10893     int fromX, fromY, toX, toY;
10894
10895     if (appData.debugMode) {
10896       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10897     }
10898
10899     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10900       return FALSE;
10901
10902     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10903       pvInfoList[currentMove].depth = programStats.depth;
10904       pvInfoList[currentMove].score = programStats.score;
10905       pvInfoList[currentMove].time  = 0;
10906       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10907     }
10908
10909     if (currentMove >= forwardMostMove) {
10910       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10911 //      gameMode = EndOfGame;
10912 //      ModeHighlight();
10913
10914       /* [AS] Clear current move marker at the end of a game */
10915       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10916
10917       return FALSE;
10918     }
10919
10920     toX = moveList[currentMove][2] - AAA;
10921     toY = moveList[currentMove][3] - ONE;
10922
10923     if (moveList[currentMove][1] == '@') {
10924         if (appData.highlightLastMove) {
10925             SetHighlights(-1, -1, toX, toY);
10926         }
10927     } else {
10928         fromX = moveList[currentMove][0] - AAA;
10929         fromY = moveList[currentMove][1] - ONE;
10930
10931         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10932
10933         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10934
10935         if (appData.highlightLastMove) {
10936             SetHighlights(fromX, fromY, toX, toY);
10937         }
10938     }
10939     DisplayMove(currentMove);
10940     SendMoveToProgram(currentMove++, &first);
10941     DisplayBothClocks();
10942     DrawPosition(FALSE, boards[currentMove]);
10943     // [HGM] PV info: always display, routine tests if empty
10944     DisplayComment(currentMove - 1, commentList[currentMove]);
10945     return TRUE;
10946 }
10947
10948
10949 int
10950 LoadGameOneMove (ChessMove readAhead)
10951 {
10952     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10953     char promoChar = NULLCHAR;
10954     ChessMove moveType;
10955     char move[MSG_SIZ];
10956     char *p, *q;
10957
10958     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10959         gameMode != AnalyzeMode && gameMode != Training) {
10960         gameFileFP = NULL;
10961         return FALSE;
10962     }
10963
10964     yyboardindex = forwardMostMove;
10965     if (readAhead != EndOfFile) {
10966       moveType = readAhead;
10967     } else {
10968       if (gameFileFP == NULL)
10969           return FALSE;
10970       moveType = (ChessMove) Myylex();
10971     }
10972
10973     done = FALSE;
10974     switch (moveType) {
10975       case Comment:
10976         if (appData.debugMode)
10977           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10978         p = yy_text;
10979
10980         /* append the comment but don't display it */
10981         AppendComment(currentMove, p, FALSE);
10982         return TRUE;
10983
10984       case WhiteCapturesEnPassant:
10985       case BlackCapturesEnPassant:
10986       case WhitePromotion:
10987       case BlackPromotion:
10988       case WhiteNonPromotion:
10989       case BlackNonPromotion:
10990       case NormalMove:
10991       case WhiteKingSideCastle:
10992       case WhiteQueenSideCastle:
10993       case BlackKingSideCastle:
10994       case BlackQueenSideCastle:
10995       case WhiteKingSideCastleWild:
10996       case WhiteQueenSideCastleWild:
10997       case BlackKingSideCastleWild:
10998       case BlackQueenSideCastleWild:
10999       /* PUSH Fabien */
11000       case WhiteHSideCastleFR:
11001       case WhiteASideCastleFR:
11002       case BlackHSideCastleFR:
11003       case BlackASideCastleFR:
11004       /* POP Fabien */
11005         if (appData.debugMode)
11006           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11007         fromX = currentMoveString[0] - AAA;
11008         fromY = currentMoveString[1] - ONE;
11009         toX = currentMoveString[2] - AAA;
11010         toY = currentMoveString[3] - ONE;
11011         promoChar = currentMoveString[4];
11012         break;
11013
11014       case WhiteDrop:
11015       case BlackDrop:
11016         if (appData.debugMode)
11017           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11018         fromX = moveType == WhiteDrop ?
11019           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11020         (int) CharToPiece(ToLower(currentMoveString[0]));
11021         fromY = DROP_RANK;
11022         toX = currentMoveString[2] - AAA;
11023         toY = currentMoveString[3] - ONE;
11024         break;
11025
11026       case WhiteWins:
11027       case BlackWins:
11028       case GameIsDrawn:
11029       case GameUnfinished:
11030         if (appData.debugMode)
11031           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11032         p = strchr(yy_text, '{');
11033         if (p == NULL) p = strchr(yy_text, '(');
11034         if (p == NULL) {
11035             p = yy_text;
11036             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11037         } else {
11038             q = strchr(p, *p == '{' ? '}' : ')');
11039             if (q != NULL) *q = NULLCHAR;
11040             p++;
11041         }
11042         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11043         GameEnds(moveType, p, GE_FILE);
11044         done = TRUE;
11045         if (cmailMsgLoaded) {
11046             ClearHighlights();
11047             flipView = WhiteOnMove(currentMove);
11048             if (moveType == GameUnfinished) flipView = !flipView;
11049             if (appData.debugMode)
11050               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11051         }
11052         break;
11053
11054       case EndOfFile:
11055         if (appData.debugMode)
11056           fprintf(debugFP, "Parser hit end of file\n");
11057         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11058           case MT_NONE:
11059           case MT_CHECK:
11060             break;
11061           case MT_CHECKMATE:
11062           case MT_STAINMATE:
11063             if (WhiteOnMove(currentMove)) {
11064                 GameEnds(BlackWins, "Black mates", GE_FILE);
11065             } else {
11066                 GameEnds(WhiteWins, "White mates", GE_FILE);
11067             }
11068             break;
11069           case MT_STALEMATE:
11070             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11071             break;
11072         }
11073         done = TRUE;
11074         break;
11075
11076       case MoveNumberOne:
11077         if (lastLoadGameStart == GNUChessGame) {
11078             /* GNUChessGames have numbers, but they aren't move numbers */
11079             if (appData.debugMode)
11080               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11081                       yy_text, (int) moveType);
11082             return LoadGameOneMove(EndOfFile); /* tail recursion */
11083         }
11084         /* else fall thru */
11085
11086       case XBoardGame:
11087       case GNUChessGame:
11088       case PGNTag:
11089         /* Reached start of next game in file */
11090         if (appData.debugMode)
11091           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11092         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11093           case MT_NONE:
11094           case MT_CHECK:
11095             break;
11096           case MT_CHECKMATE:
11097           case MT_STAINMATE:
11098             if (WhiteOnMove(currentMove)) {
11099                 GameEnds(BlackWins, "Black mates", GE_FILE);
11100             } else {
11101                 GameEnds(WhiteWins, "White mates", GE_FILE);
11102             }
11103             break;
11104           case MT_STALEMATE:
11105             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11106             break;
11107         }
11108         done = TRUE;
11109         break;
11110
11111       case PositionDiagram:     /* should not happen; ignore */
11112       case ElapsedTime:         /* ignore */
11113       case NAG:                 /* ignore */
11114         if (appData.debugMode)
11115           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11116                   yy_text, (int) moveType);
11117         return LoadGameOneMove(EndOfFile); /* tail recursion */
11118
11119       case IllegalMove:
11120         if (appData.testLegality) {
11121             if (appData.debugMode)
11122               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11123             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11124                     (forwardMostMove / 2) + 1,
11125                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11126             DisplayError(move, 0);
11127             done = TRUE;
11128         } else {
11129             if (appData.debugMode)
11130               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11131                       yy_text, currentMoveString);
11132             fromX = currentMoveString[0] - AAA;
11133             fromY = currentMoveString[1] - ONE;
11134             toX = currentMoveString[2] - AAA;
11135             toY = currentMoveString[3] - ONE;
11136             promoChar = currentMoveString[4];
11137         }
11138         break;
11139
11140       case AmbiguousMove:
11141         if (appData.debugMode)
11142           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11143         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11144                 (forwardMostMove / 2) + 1,
11145                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11146         DisplayError(move, 0);
11147         done = TRUE;
11148         break;
11149
11150       default:
11151       case ImpossibleMove:
11152         if (appData.debugMode)
11153           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11154         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11155                 (forwardMostMove / 2) + 1,
11156                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11157         DisplayError(move, 0);
11158         done = TRUE;
11159         break;
11160     }
11161
11162     if (done) {
11163         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11164             DrawPosition(FALSE, boards[currentMove]);
11165             DisplayBothClocks();
11166             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11167               DisplayComment(currentMove - 1, commentList[currentMove]);
11168         }
11169         (void) StopLoadGameTimer();
11170         gameFileFP = NULL;
11171         cmailOldMove = forwardMostMove;
11172         return FALSE;
11173     } else {
11174         /* currentMoveString is set as a side-effect of yylex */
11175
11176         thinkOutput[0] = NULLCHAR;
11177         MakeMove(fromX, fromY, toX, toY, promoChar);
11178         currentMove = forwardMostMove;
11179         return TRUE;
11180     }
11181 }
11182
11183 /* Load the nth game from the given file */
11184 int
11185 LoadGameFromFile (char *filename, int n, char *title, int useList)
11186 {
11187     FILE *f;
11188     char buf[MSG_SIZ];
11189
11190     if (strcmp(filename, "-") == 0) {
11191         f = stdin;
11192         title = "stdin";
11193     } else {
11194         f = fopen(filename, "rb");
11195         if (f == NULL) {
11196           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11197             DisplayError(buf, errno);
11198             return FALSE;
11199         }
11200     }
11201     if (fseek(f, 0, 0) == -1) {
11202         /* f is not seekable; probably a pipe */
11203         useList = FALSE;
11204     }
11205     if (useList && n == 0) {
11206         int error = GameListBuild(f);
11207         if (error) {
11208             DisplayError(_("Cannot build game list"), error);
11209         } else if (!ListEmpty(&gameList) &&
11210                    ((ListGame *) gameList.tailPred)->number > 1) {
11211             GameListPopUp(f, title);
11212             return TRUE;
11213         }
11214         GameListDestroy();
11215         n = 1;
11216     }
11217     if (n == 0) n = 1;
11218     return LoadGame(f, n, title, FALSE);
11219 }
11220
11221
11222 void
11223 MakeRegisteredMove ()
11224 {
11225     int fromX, fromY, toX, toY;
11226     char promoChar;
11227     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11228         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11229           case CMAIL_MOVE:
11230           case CMAIL_DRAW:
11231             if (appData.debugMode)
11232               fprintf(debugFP, "Restoring %s for game %d\n",
11233                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11234
11235             thinkOutput[0] = NULLCHAR;
11236             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11237             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11238             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11239             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11240             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11241             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11242             MakeMove(fromX, fromY, toX, toY, promoChar);
11243             ShowMove(fromX, fromY, toX, toY);
11244
11245             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11246               case MT_NONE:
11247               case MT_CHECK:
11248                 break;
11249
11250               case MT_CHECKMATE:
11251               case MT_STAINMATE:
11252                 if (WhiteOnMove(currentMove)) {
11253                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11254                 } else {
11255                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11256                 }
11257                 break;
11258
11259               case MT_STALEMATE:
11260                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11261                 break;
11262             }
11263
11264             break;
11265
11266           case CMAIL_RESIGN:
11267             if (WhiteOnMove(currentMove)) {
11268                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11269             } else {
11270                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11271             }
11272             break;
11273
11274           case CMAIL_ACCEPT:
11275             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11276             break;
11277
11278           default:
11279             break;
11280         }
11281     }
11282
11283     return;
11284 }
11285
11286 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11287 int
11288 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11289 {
11290     int retVal;
11291
11292     if (gameNumber > nCmailGames) {
11293         DisplayError(_("No more games in this message"), 0);
11294         return FALSE;
11295     }
11296     if (f == lastLoadGameFP) {
11297         int offset = gameNumber - lastLoadGameNumber;
11298         if (offset == 0) {
11299             cmailMsg[0] = NULLCHAR;
11300             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11301                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11302                 nCmailMovesRegistered--;
11303             }
11304             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11305             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11306                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11307             }
11308         } else {
11309             if (! RegisterMove()) return FALSE;
11310         }
11311     }
11312
11313     retVal = LoadGame(f, gameNumber, title, useList);
11314
11315     /* Make move registered during previous look at this game, if any */
11316     MakeRegisteredMove();
11317
11318     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11319         commentList[currentMove]
11320           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11321         DisplayComment(currentMove - 1, commentList[currentMove]);
11322     }
11323
11324     return retVal;
11325 }
11326
11327 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11328 int
11329 ReloadGame (int offset)
11330 {
11331     int gameNumber = lastLoadGameNumber + offset;
11332     if (lastLoadGameFP == NULL) {
11333         DisplayError(_("No game has been loaded yet"), 0);
11334         return FALSE;
11335     }
11336     if (gameNumber <= 0) {
11337         DisplayError(_("Can't back up any further"), 0);
11338         return FALSE;
11339     }
11340     if (cmailMsgLoaded) {
11341         return CmailLoadGame(lastLoadGameFP, gameNumber,
11342                              lastLoadGameTitle, lastLoadGameUseList);
11343     } else {
11344         return LoadGame(lastLoadGameFP, gameNumber,
11345                         lastLoadGameTitle, lastLoadGameUseList);
11346     }
11347 }
11348
11349 int keys[EmptySquare+1];
11350
11351 int
11352 PositionMatches (Board b1, Board b2)
11353 {
11354     int r, f, sum=0;
11355     switch(appData.searchMode) {
11356         case 1: return CompareWithRights(b1, b2);
11357         case 2:
11358             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11359                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11360             }
11361             return TRUE;
11362         case 3:
11363             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11364               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11365                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11366             }
11367             return sum==0;
11368         case 4:
11369             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11370                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11371             }
11372             return sum==0;
11373     }
11374     return TRUE;
11375 }
11376
11377 #define Q_PROMO  4
11378 #define Q_EP     3
11379 #define Q_BCASTL 2
11380 #define Q_WCASTL 1
11381
11382 int pieceList[256], quickBoard[256];
11383 ChessSquare pieceType[256] = { EmptySquare };
11384 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11385 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11386 int soughtTotal, turn;
11387 Boolean epOK, flipSearch;
11388
11389 typedef struct {
11390     unsigned char piece, to;
11391 } Move;
11392
11393 #define DSIZE (250000)
11394
11395 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11396 Move *moveDatabase = initialSpace;
11397 unsigned int movePtr, dataSize = DSIZE;
11398
11399 int
11400 MakePieceList (Board board, int *counts)
11401 {
11402     int r, f, n=Q_PROMO, total=0;
11403     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11404     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11405         int sq = f + (r<<4);
11406         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11407             quickBoard[sq] = ++n;
11408             pieceList[n] = sq;
11409             pieceType[n] = board[r][f];
11410             counts[board[r][f]]++;
11411             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11412             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11413             total++;
11414         }
11415     }
11416     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11417     return total;
11418 }
11419
11420 void
11421 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11422 {
11423     int sq = fromX + (fromY<<4);
11424     int piece = quickBoard[sq];
11425     quickBoard[sq] = 0;
11426     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11427     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11428         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11429         moveDatabase[movePtr++].piece = Q_WCASTL;
11430         quickBoard[sq] = piece;
11431         piece = quickBoard[from]; quickBoard[from] = 0;
11432         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11433     } else
11434     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11435         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11436         moveDatabase[movePtr++].piece = Q_BCASTL;
11437         quickBoard[sq] = piece;
11438         piece = quickBoard[from]; quickBoard[from] = 0;
11439         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11440     } else
11441     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11442         quickBoard[(fromY<<4)+toX] = 0;
11443         moveDatabase[movePtr].piece = Q_EP;
11444         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11445         moveDatabase[movePtr].to = sq;
11446     } else
11447     if(promoPiece != pieceType[piece]) {
11448         moveDatabase[movePtr++].piece = Q_PROMO;
11449         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11450     }
11451     moveDatabase[movePtr].piece = piece;
11452     quickBoard[sq] = piece;
11453     movePtr++;
11454 }
11455
11456 int
11457 PackGame (Board board)
11458 {
11459     Move *newSpace = NULL;
11460     moveDatabase[movePtr].piece = 0; // terminate previous game
11461     if(movePtr > dataSize) {
11462         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11463         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11464         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11465         if(newSpace) {
11466             int i;
11467             Move *p = moveDatabase, *q = newSpace;
11468             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11469             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11470             moveDatabase = newSpace;
11471         } else { // calloc failed, we must be out of memory. Too bad...
11472             dataSize = 0; // prevent calloc events for all subsequent games
11473             return 0;     // and signal this one isn't cached
11474         }
11475     }
11476     movePtr++;
11477     MakePieceList(board, counts);
11478     return movePtr;
11479 }
11480
11481 int
11482 QuickCompare (Board board, int *minCounts, int *maxCounts)
11483 {   // compare according to search mode
11484     int r, f;
11485     switch(appData.searchMode)
11486     {
11487       case 1: // exact position match
11488         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11489         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11490             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11491         }
11492         break;
11493       case 2: // can have extra material on empty squares
11494         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11495             if(board[r][f] == EmptySquare) continue;
11496             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11497         }
11498         break;
11499       case 3: // material with exact Pawn structure
11500         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11501             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11502             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11503         } // fall through to material comparison
11504       case 4: // exact material
11505         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11506         break;
11507       case 6: // material range with given imbalance
11508         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11509         // fall through to range comparison
11510       case 5: // material range
11511         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11512     }
11513     return TRUE;
11514 }
11515
11516 int
11517 QuickScan (Board board, Move *move)
11518 {   // reconstruct game,and compare all positions in it
11519     int cnt=0, stretch=0, total = MakePieceList(board, counts), delayedKing = -1;
11520     do {
11521         int piece = move->piece;
11522         int to = move->to, from = pieceList[piece];
11523         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11524           if(!piece) return -1;
11525           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11526             piece = (++move)->piece;
11527             from = pieceList[piece];
11528             counts[pieceType[piece]]--;
11529             pieceType[piece] = (ChessSquare) move->to;
11530             counts[move->to]++;
11531           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11532             counts[pieceType[quickBoard[to]]]--;
11533             quickBoard[to] = 0; total--;
11534             move++;
11535             continue;
11536           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11537             int rook;
11538             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11539             from  = pieceList[piece]; // so this must be King
11540             quickBoard[from] = 0;
11541             pieceList[piece] = to;
11542             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11543             quickBoard[from] = 0; // rook
11544             quickBoard[to] = piece;
11545             to = move->to; piece = move->piece;
11546             goto aftercastle;
11547           }
11548         }
11549         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11550         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11551         quickBoard[from] = 0;
11552       aftercastle:
11553         quickBoard[to] = piece;
11554         pieceList[piece] = to;
11555         cnt++; turn ^= 3;
11556         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11557            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11558            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11559                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11560           ) {
11561             static int lastCounts[EmptySquare+1];
11562             int i;
11563             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11564             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11565         } else stretch = 0;
11566         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11567         move++; delayedKing = -1;
11568     } while(1);
11569 }
11570
11571 void
11572 InitSearch ()
11573 {
11574     int r, f;
11575     flipSearch = FALSE;
11576     CopyBoard(soughtBoard, boards[currentMove]);
11577     soughtTotal = MakePieceList(soughtBoard, maxSought);
11578     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11579     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11580     CopyBoard(reverseBoard, boards[currentMove]);
11581     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11582         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11583         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11584         reverseBoard[r][f] = piece;
11585     }
11586     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11587     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11588     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11589                  || (boards[currentMove][CASTLING][2] == NoRights || 
11590                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11591                  && (boards[currentMove][CASTLING][5] == NoRights || 
11592                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11593       ) {
11594         flipSearch = TRUE;
11595         CopyBoard(flipBoard, soughtBoard);
11596         CopyBoard(rotateBoard, reverseBoard);
11597         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11598             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11599             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11600         }
11601     }
11602     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11603     if(appData.searchMode >= 5) {
11604         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11605         MakePieceList(soughtBoard, minSought);
11606         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11607     }
11608     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11609         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11610 }
11611
11612 GameInfo dummyInfo;
11613
11614 int
11615 GameContainsPosition (FILE *f, ListGame *lg)
11616 {
11617     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11618     int fromX, fromY, toX, toY;
11619     char promoChar;
11620     static int initDone=FALSE;
11621
11622     // weed out games based on numerical tag comparison
11623     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11624     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11625     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11626     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11627     if(!initDone) {
11628         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11629         initDone = TRUE;
11630     }
11631     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11632     else CopyBoard(boards[scratch], initialPosition); // default start position
11633     if(lg->moves) {
11634         turn = btm + 1;
11635         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11636         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11637     }
11638     if(btm) plyNr++;
11639     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11640     fseek(f, lg->offset, 0);
11641     yynewfile(f);
11642     while(1) {
11643         yyboardindex = scratch;
11644         quickFlag = plyNr+1;
11645         next = Myylex();
11646         quickFlag = 0;
11647         switch(next) {
11648             case PGNTag:
11649                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11650             default:
11651                 continue;
11652
11653             case XBoardGame:
11654             case GNUChessGame:
11655                 if(plyNr) return -1; // after we have seen moves, this is for new game
11656               continue;
11657
11658             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11659             case ImpossibleMove:
11660             case WhiteWins: // game ends here with these four
11661             case BlackWins:
11662             case GameIsDrawn:
11663             case GameUnfinished:
11664                 return -1;
11665
11666             case IllegalMove:
11667                 if(appData.testLegality) return -1;
11668             case WhiteCapturesEnPassant:
11669             case BlackCapturesEnPassant:
11670             case WhitePromotion:
11671             case BlackPromotion:
11672             case WhiteNonPromotion:
11673             case BlackNonPromotion:
11674             case NormalMove:
11675             case WhiteKingSideCastle:
11676             case WhiteQueenSideCastle:
11677             case BlackKingSideCastle:
11678             case BlackQueenSideCastle:
11679             case WhiteKingSideCastleWild:
11680             case WhiteQueenSideCastleWild:
11681             case BlackKingSideCastleWild:
11682             case BlackQueenSideCastleWild:
11683             case WhiteHSideCastleFR:
11684             case WhiteASideCastleFR:
11685             case BlackHSideCastleFR:
11686             case BlackASideCastleFR:
11687                 fromX = currentMoveString[0] - AAA;
11688                 fromY = currentMoveString[1] - ONE;
11689                 toX = currentMoveString[2] - AAA;
11690                 toY = currentMoveString[3] - ONE;
11691                 promoChar = currentMoveString[4];
11692                 break;
11693             case WhiteDrop:
11694             case BlackDrop:
11695                 fromX = next == WhiteDrop ?
11696                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11697                   (int) CharToPiece(ToLower(currentMoveString[0]));
11698                 fromY = DROP_RANK;
11699                 toX = currentMoveString[2] - AAA;
11700                 toY = currentMoveString[3] - ONE;
11701                 promoChar = 0;
11702                 break;
11703         }
11704         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11705         plyNr++;
11706         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11707         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11708         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11709         if(appData.findMirror) {
11710             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11711             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11712         }
11713     }
11714 }
11715
11716 /* Load the nth game from open file f */
11717 int
11718 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11719 {
11720     ChessMove cm;
11721     char buf[MSG_SIZ];
11722     int gn = gameNumber;
11723     ListGame *lg = NULL;
11724     int numPGNTags = 0;
11725     int err, pos = -1;
11726     GameMode oldGameMode;
11727     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11728
11729     if (appData.debugMode)
11730         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11731
11732     if (gameMode == Training )
11733         SetTrainingModeOff();
11734
11735     oldGameMode = gameMode;
11736     if (gameMode != BeginningOfGame) {
11737       Reset(FALSE, TRUE);
11738     }
11739
11740     gameFileFP = f;
11741     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11742         fclose(lastLoadGameFP);
11743     }
11744
11745     if (useList) {
11746         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11747
11748         if (lg) {
11749             fseek(f, lg->offset, 0);
11750             GameListHighlight(gameNumber);
11751             pos = lg->position;
11752             gn = 1;
11753         }
11754         else {
11755             DisplayError(_("Game number out of range"), 0);
11756             return FALSE;
11757         }
11758     } else {
11759         GameListDestroy();
11760         if (fseek(f, 0, 0) == -1) {
11761             if (f == lastLoadGameFP ?
11762                 gameNumber == lastLoadGameNumber + 1 :
11763                 gameNumber == 1) {
11764                 gn = 1;
11765             } else {
11766                 DisplayError(_("Can't seek on game file"), 0);
11767                 return FALSE;
11768             }
11769         }
11770     }
11771     lastLoadGameFP = f;
11772     lastLoadGameNumber = gameNumber;
11773     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11774     lastLoadGameUseList = useList;
11775
11776     yynewfile(f);
11777
11778     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11779       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11780                 lg->gameInfo.black);
11781             DisplayTitle(buf);
11782     } else if (*title != NULLCHAR) {
11783         if (gameNumber > 1) {
11784           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11785             DisplayTitle(buf);
11786         } else {
11787             DisplayTitle(title);
11788         }
11789     }
11790
11791     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11792         gameMode = PlayFromGameFile;
11793         ModeHighlight();
11794     }
11795
11796     currentMove = forwardMostMove = backwardMostMove = 0;
11797     CopyBoard(boards[0], initialPosition);
11798     StopClocks();
11799
11800     /*
11801      * Skip the first gn-1 games in the file.
11802      * Also skip over anything that precedes an identifiable
11803      * start of game marker, to avoid being confused by
11804      * garbage at the start of the file.  Currently
11805      * recognized start of game markers are the move number "1",
11806      * the pattern "gnuchess .* game", the pattern
11807      * "^[#;%] [^ ]* game file", and a PGN tag block.
11808      * A game that starts with one of the latter two patterns
11809      * will also have a move number 1, possibly
11810      * following a position diagram.
11811      * 5-4-02: Let's try being more lenient and allowing a game to
11812      * start with an unnumbered move.  Does that break anything?
11813      */
11814     cm = lastLoadGameStart = EndOfFile;
11815     while (gn > 0) {
11816         yyboardindex = forwardMostMove;
11817         cm = (ChessMove) Myylex();
11818         switch (cm) {
11819           case EndOfFile:
11820             if (cmailMsgLoaded) {
11821                 nCmailGames = CMAIL_MAX_GAMES - gn;
11822             } else {
11823                 Reset(TRUE, TRUE);
11824                 DisplayError(_("Game not found in file"), 0);
11825             }
11826             return FALSE;
11827
11828           case GNUChessGame:
11829           case XBoardGame:
11830             gn--;
11831             lastLoadGameStart = cm;
11832             break;
11833
11834           case MoveNumberOne:
11835             switch (lastLoadGameStart) {
11836               case GNUChessGame:
11837               case XBoardGame:
11838               case PGNTag:
11839                 break;
11840               case MoveNumberOne:
11841               case EndOfFile:
11842                 gn--;           /* count this game */
11843                 lastLoadGameStart = cm;
11844                 break;
11845               default:
11846                 /* impossible */
11847                 break;
11848             }
11849             break;
11850
11851           case PGNTag:
11852             switch (lastLoadGameStart) {
11853               case GNUChessGame:
11854               case PGNTag:
11855               case MoveNumberOne:
11856               case EndOfFile:
11857                 gn--;           /* count this game */
11858                 lastLoadGameStart = cm;
11859                 break;
11860               case XBoardGame:
11861                 lastLoadGameStart = cm; /* game counted already */
11862                 break;
11863               default:
11864                 /* impossible */
11865                 break;
11866             }
11867             if (gn > 0) {
11868                 do {
11869                     yyboardindex = forwardMostMove;
11870                     cm = (ChessMove) Myylex();
11871                 } while (cm == PGNTag || cm == Comment);
11872             }
11873             break;
11874
11875           case WhiteWins:
11876           case BlackWins:
11877           case GameIsDrawn:
11878             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11879                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11880                     != CMAIL_OLD_RESULT) {
11881                     nCmailResults ++ ;
11882                     cmailResult[  CMAIL_MAX_GAMES
11883                                 - gn - 1] = CMAIL_OLD_RESULT;
11884                 }
11885             }
11886             break;
11887
11888           case NormalMove:
11889             /* Only a NormalMove can be at the start of a game
11890              * without a position diagram. */
11891             if (lastLoadGameStart == EndOfFile ) {
11892               gn--;
11893               lastLoadGameStart = MoveNumberOne;
11894             }
11895             break;
11896
11897           default:
11898             break;
11899         }
11900     }
11901
11902     if (appData.debugMode)
11903       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11904
11905     if (cm == XBoardGame) {
11906         /* Skip any header junk before position diagram and/or move 1 */
11907         for (;;) {
11908             yyboardindex = forwardMostMove;
11909             cm = (ChessMove) Myylex();
11910
11911             if (cm == EndOfFile ||
11912                 cm == GNUChessGame || cm == XBoardGame) {
11913                 /* Empty game; pretend end-of-file and handle later */
11914                 cm = EndOfFile;
11915                 break;
11916             }
11917
11918             if (cm == MoveNumberOne || cm == PositionDiagram ||
11919                 cm == PGNTag || cm == Comment)
11920               break;
11921         }
11922     } else if (cm == GNUChessGame) {
11923         if (gameInfo.event != NULL) {
11924             free(gameInfo.event);
11925         }
11926         gameInfo.event = StrSave(yy_text);
11927     }
11928
11929     startedFromSetupPosition = FALSE;
11930     while (cm == PGNTag) {
11931         if (appData.debugMode)
11932           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11933         err = ParsePGNTag(yy_text, &gameInfo);
11934         if (!err) numPGNTags++;
11935
11936         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11937         if(gameInfo.variant != oldVariant) {
11938             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11939             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11940             InitPosition(TRUE);
11941             oldVariant = gameInfo.variant;
11942             if (appData.debugMode)
11943               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11944         }
11945
11946
11947         if (gameInfo.fen != NULL) {
11948           Board initial_position;
11949           startedFromSetupPosition = TRUE;
11950           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11951             Reset(TRUE, TRUE);
11952             DisplayError(_("Bad FEN position in file"), 0);
11953             return FALSE;
11954           }
11955           CopyBoard(boards[0], initial_position);
11956           if (blackPlaysFirst) {
11957             currentMove = forwardMostMove = backwardMostMove = 1;
11958             CopyBoard(boards[1], initial_position);
11959             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11960             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11961             timeRemaining[0][1] = whiteTimeRemaining;
11962             timeRemaining[1][1] = blackTimeRemaining;
11963             if (commentList[0] != NULL) {
11964               commentList[1] = commentList[0];
11965               commentList[0] = NULL;
11966             }
11967           } else {
11968             currentMove = forwardMostMove = backwardMostMove = 0;
11969           }
11970           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11971           {   int i;
11972               initialRulePlies = FENrulePlies;
11973               for( i=0; i< nrCastlingRights; i++ )
11974                   initialRights[i] = initial_position[CASTLING][i];
11975           }
11976           yyboardindex = forwardMostMove;
11977           free(gameInfo.fen);
11978           gameInfo.fen = NULL;
11979         }
11980
11981         yyboardindex = forwardMostMove;
11982         cm = (ChessMove) Myylex();
11983
11984         /* Handle comments interspersed among the tags */
11985         while (cm == Comment) {
11986             char *p;
11987             if (appData.debugMode)
11988               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11989             p = yy_text;
11990             AppendComment(currentMove, p, FALSE);
11991             yyboardindex = forwardMostMove;
11992             cm = (ChessMove) Myylex();
11993         }
11994     }
11995
11996     /* don't rely on existence of Event tag since if game was
11997      * pasted from clipboard the Event tag may not exist
11998      */
11999     if (numPGNTags > 0){
12000         char *tags;
12001         if (gameInfo.variant == VariantNormal) {
12002           VariantClass v = StringToVariant(gameInfo.event);
12003           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12004           if(v < VariantShogi) gameInfo.variant = v;
12005         }
12006         if (!matchMode) {
12007           if( appData.autoDisplayTags ) {
12008             tags = PGNTags(&gameInfo);
12009             TagsPopUp(tags, CmailMsg());
12010             free(tags);
12011           }
12012         }
12013     } else {
12014         /* Make something up, but don't display it now */
12015         SetGameInfo();
12016         TagsPopDown();
12017     }
12018
12019     if (cm == PositionDiagram) {
12020         int i, j;
12021         char *p;
12022         Board initial_position;
12023
12024         if (appData.debugMode)
12025           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12026
12027         if (!startedFromSetupPosition) {
12028             p = yy_text;
12029             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12030               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12031                 switch (*p) {
12032                   case '{':
12033                   case '[':
12034                   case '-':
12035                   case ' ':
12036                   case '\t':
12037                   case '\n':
12038                   case '\r':
12039                     break;
12040                   default:
12041                     initial_position[i][j++] = CharToPiece(*p);
12042                     break;
12043                 }
12044             while (*p == ' ' || *p == '\t' ||
12045                    *p == '\n' || *p == '\r') p++;
12046
12047             if (strncmp(p, "black", strlen("black"))==0)
12048               blackPlaysFirst = TRUE;
12049             else
12050               blackPlaysFirst = FALSE;
12051             startedFromSetupPosition = TRUE;
12052
12053             CopyBoard(boards[0], initial_position);
12054             if (blackPlaysFirst) {
12055                 currentMove = forwardMostMove = backwardMostMove = 1;
12056                 CopyBoard(boards[1], initial_position);
12057                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12058                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12059                 timeRemaining[0][1] = whiteTimeRemaining;
12060                 timeRemaining[1][1] = blackTimeRemaining;
12061                 if (commentList[0] != NULL) {
12062                     commentList[1] = commentList[0];
12063                     commentList[0] = NULL;
12064                 }
12065             } else {
12066                 currentMove = forwardMostMove = backwardMostMove = 0;
12067             }
12068         }
12069         yyboardindex = forwardMostMove;
12070         cm = (ChessMove) Myylex();
12071     }
12072
12073     if (first.pr == NoProc) {
12074         StartChessProgram(&first);
12075     }
12076     InitChessProgram(&first, FALSE);
12077     SendToProgram("force\n", &first);
12078     if (startedFromSetupPosition) {
12079         SendBoard(&first, forwardMostMove);
12080     if (appData.debugMode) {
12081         fprintf(debugFP, "Load Game\n");
12082     }
12083         DisplayBothClocks();
12084     }
12085
12086     /* [HGM] server: flag to write setup moves in broadcast file as one */
12087     loadFlag = appData.suppressLoadMoves;
12088
12089     while (cm == Comment) {
12090         char *p;
12091         if (appData.debugMode)
12092           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12093         p = yy_text;
12094         AppendComment(currentMove, p, FALSE);
12095         yyboardindex = forwardMostMove;
12096         cm = (ChessMove) Myylex();
12097     }
12098
12099     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12100         cm == WhiteWins || cm == BlackWins ||
12101         cm == GameIsDrawn || cm == GameUnfinished) {
12102         DisplayMessage("", _("No moves in game"));
12103         if (cmailMsgLoaded) {
12104             if (appData.debugMode)
12105               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12106             ClearHighlights();
12107             flipView = FALSE;
12108         }
12109         DrawPosition(FALSE, boards[currentMove]);
12110         DisplayBothClocks();
12111         gameMode = EditGame;
12112         ModeHighlight();
12113         gameFileFP = NULL;
12114         cmailOldMove = 0;
12115         return TRUE;
12116     }
12117
12118     // [HGM] PV info: routine tests if comment empty
12119     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12120         DisplayComment(currentMove - 1, commentList[currentMove]);
12121     }
12122     if (!matchMode && appData.timeDelay != 0)
12123       DrawPosition(FALSE, boards[currentMove]);
12124
12125     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12126       programStats.ok_to_send = 1;
12127     }
12128
12129     /* if the first token after the PGN tags is a move
12130      * and not move number 1, retrieve it from the parser
12131      */
12132     if (cm != MoveNumberOne)
12133         LoadGameOneMove(cm);
12134
12135     /* load the remaining moves from the file */
12136     while (LoadGameOneMove(EndOfFile)) {
12137       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12138       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12139     }
12140
12141     /* rewind to the start of the game */
12142     currentMove = backwardMostMove;
12143
12144     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12145
12146     if (oldGameMode == AnalyzeFile ||
12147         oldGameMode == AnalyzeMode) {
12148       AnalyzeFileEvent();
12149     }
12150
12151     if (!matchMode && pos >= 0) {
12152         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12153     } else
12154     if (matchMode || appData.timeDelay == 0) {
12155       ToEndEvent();
12156     } else if (appData.timeDelay > 0) {
12157       AutoPlayGameLoop();
12158     }
12159
12160     if (appData.debugMode)
12161         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12162
12163     loadFlag = 0; /* [HGM] true game starts */
12164     return TRUE;
12165 }
12166
12167 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12168 int
12169 ReloadPosition (int offset)
12170 {
12171     int positionNumber = lastLoadPositionNumber + offset;
12172     if (lastLoadPositionFP == NULL) {
12173         DisplayError(_("No position has been loaded yet"), 0);
12174         return FALSE;
12175     }
12176     if (positionNumber <= 0) {
12177         DisplayError(_("Can't back up any further"), 0);
12178         return FALSE;
12179     }
12180     return LoadPosition(lastLoadPositionFP, positionNumber,
12181                         lastLoadPositionTitle);
12182 }
12183
12184 /* Load the nth position from the given file */
12185 int
12186 LoadPositionFromFile (char *filename, int n, char *title)
12187 {
12188     FILE *f;
12189     char buf[MSG_SIZ];
12190
12191     if (strcmp(filename, "-") == 0) {
12192         return LoadPosition(stdin, n, "stdin");
12193     } else {
12194         f = fopen(filename, "rb");
12195         if (f == NULL) {
12196             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12197             DisplayError(buf, errno);
12198             return FALSE;
12199         } else {
12200             return LoadPosition(f, n, title);
12201         }
12202     }
12203 }
12204
12205 /* Load the nth position from the given open file, and close it */
12206 int
12207 LoadPosition (FILE *f, int positionNumber, char *title)
12208 {
12209     char *p, line[MSG_SIZ];
12210     Board initial_position;
12211     int i, j, fenMode, pn;
12212
12213     if (gameMode == Training )
12214         SetTrainingModeOff();
12215
12216     if (gameMode != BeginningOfGame) {
12217         Reset(FALSE, TRUE);
12218     }
12219     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12220         fclose(lastLoadPositionFP);
12221     }
12222     if (positionNumber == 0) positionNumber = 1;
12223     lastLoadPositionFP = f;
12224     lastLoadPositionNumber = positionNumber;
12225     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12226     if (first.pr == NoProc && !appData.noChessProgram) {
12227       StartChessProgram(&first);
12228       InitChessProgram(&first, FALSE);
12229     }
12230     pn = positionNumber;
12231     if (positionNumber < 0) {
12232         /* Negative position number means to seek to that byte offset */
12233         if (fseek(f, -positionNumber, 0) == -1) {
12234             DisplayError(_("Can't seek on position file"), 0);
12235             return FALSE;
12236         };
12237         pn = 1;
12238     } else {
12239         if (fseek(f, 0, 0) == -1) {
12240             if (f == lastLoadPositionFP ?
12241                 positionNumber == lastLoadPositionNumber + 1 :
12242                 positionNumber == 1) {
12243                 pn = 1;
12244             } else {
12245                 DisplayError(_("Can't seek on position file"), 0);
12246                 return FALSE;
12247             }
12248         }
12249     }
12250     /* See if this file is FEN or old-style xboard */
12251     if (fgets(line, MSG_SIZ, f) == NULL) {
12252         DisplayError(_("Position not found in file"), 0);
12253         return FALSE;
12254     }
12255     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12256     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12257
12258     if (pn >= 2) {
12259         if (fenMode || line[0] == '#') pn--;
12260         while (pn > 0) {
12261             /* skip positions before number pn */
12262             if (fgets(line, MSG_SIZ, f) == NULL) {
12263                 Reset(TRUE, TRUE);
12264                 DisplayError(_("Position not found in file"), 0);
12265                 return FALSE;
12266             }
12267             if (fenMode || line[0] == '#') pn--;
12268         }
12269     }
12270
12271     if (fenMode) {
12272         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12273             DisplayError(_("Bad FEN position in file"), 0);
12274             return FALSE;
12275         }
12276     } else {
12277         (void) fgets(line, MSG_SIZ, f);
12278         (void) fgets(line, MSG_SIZ, f);
12279
12280         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12281             (void) fgets(line, MSG_SIZ, f);
12282             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12283                 if (*p == ' ')
12284                   continue;
12285                 initial_position[i][j++] = CharToPiece(*p);
12286             }
12287         }
12288
12289         blackPlaysFirst = FALSE;
12290         if (!feof(f)) {
12291             (void) fgets(line, MSG_SIZ, f);
12292             if (strncmp(line, "black", strlen("black"))==0)
12293               blackPlaysFirst = TRUE;
12294         }
12295     }
12296     startedFromSetupPosition = TRUE;
12297
12298     CopyBoard(boards[0], initial_position);
12299     if (blackPlaysFirst) {
12300         currentMove = forwardMostMove = backwardMostMove = 1;
12301         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12302         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12303         CopyBoard(boards[1], initial_position);
12304         DisplayMessage("", _("Black to play"));
12305     } else {
12306         currentMove = forwardMostMove = backwardMostMove = 0;
12307         DisplayMessage("", _("White to play"));
12308     }
12309     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12310     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12311         SendToProgram("force\n", &first);
12312         SendBoard(&first, forwardMostMove);
12313     }
12314     if (appData.debugMode) {
12315 int i, j;
12316   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12317   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12318         fprintf(debugFP, "Load Position\n");
12319     }
12320
12321     if (positionNumber > 1) {
12322       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12323         DisplayTitle(line);
12324     } else {
12325         DisplayTitle(title);
12326     }
12327     gameMode = EditGame;
12328     ModeHighlight();
12329     ResetClocks();
12330     timeRemaining[0][1] = whiteTimeRemaining;
12331     timeRemaining[1][1] = blackTimeRemaining;
12332     DrawPosition(FALSE, boards[currentMove]);
12333
12334     return TRUE;
12335 }
12336
12337
12338 void
12339 CopyPlayerNameIntoFileName (char **dest, char *src)
12340 {
12341     while (*src != NULLCHAR && *src != ',') {
12342         if (*src == ' ') {
12343             *(*dest)++ = '_';
12344             src++;
12345         } else {
12346             *(*dest)++ = *src++;
12347         }
12348     }
12349 }
12350
12351 char *
12352 DefaultFileName (char *ext)
12353 {
12354     static char def[MSG_SIZ];
12355     char *p;
12356
12357     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12358         p = def;
12359         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12360         *p++ = '-';
12361         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12362         *p++ = '.';
12363         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12364     } else {
12365         def[0] = NULLCHAR;
12366     }
12367     return def;
12368 }
12369
12370 /* Save the current game to the given file */
12371 int
12372 SaveGameToFile (char *filename, int append)
12373 {
12374     FILE *f;
12375     char buf[MSG_SIZ];
12376     int result, i, t,tot=0;
12377
12378     if (strcmp(filename, "-") == 0) {
12379         return SaveGame(stdout, 0, NULL);
12380     } else {
12381         for(i=0; i<10; i++) { // upto 10 tries
12382              f = fopen(filename, append ? "a" : "w");
12383              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12384              if(f || errno != 13) break;
12385              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12386              tot += t;
12387         }
12388         if (f == NULL) {
12389             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12390             DisplayError(buf, errno);
12391             return FALSE;
12392         } else {
12393             safeStrCpy(buf, lastMsg, MSG_SIZ);
12394             DisplayMessage(_("Waiting for access to save file"), "");
12395             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12396             DisplayMessage(_("Saving game"), "");
12397             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12398             result = SaveGame(f, 0, NULL);
12399             DisplayMessage(buf, "");
12400             return result;
12401         }
12402     }
12403 }
12404
12405 char *
12406 SavePart (char *str)
12407 {
12408     static char buf[MSG_SIZ];
12409     char *p;
12410
12411     p = strchr(str, ' ');
12412     if (p == NULL) return str;
12413     strncpy(buf, str, p - str);
12414     buf[p - str] = NULLCHAR;
12415     return buf;
12416 }
12417
12418 #define PGN_MAX_LINE 75
12419
12420 #define PGN_SIDE_WHITE  0
12421 #define PGN_SIDE_BLACK  1
12422
12423 static int
12424 FindFirstMoveOutOfBook (int side)
12425 {
12426     int result = -1;
12427
12428     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12429         int index = backwardMostMove;
12430         int has_book_hit = 0;
12431
12432         if( (index % 2) != side ) {
12433             index++;
12434         }
12435
12436         while( index < forwardMostMove ) {
12437             /* Check to see if engine is in book */
12438             int depth = pvInfoList[index].depth;
12439             int score = pvInfoList[index].score;
12440             int in_book = 0;
12441
12442             if( depth <= 2 ) {
12443                 in_book = 1;
12444             }
12445             else if( score == 0 && depth == 63 ) {
12446                 in_book = 1; /* Zappa */
12447             }
12448             else if( score == 2 && depth == 99 ) {
12449                 in_book = 1; /* Abrok */
12450             }
12451
12452             has_book_hit += in_book;
12453
12454             if( ! in_book ) {
12455                 result = index;
12456
12457                 break;
12458             }
12459
12460             index += 2;
12461         }
12462     }
12463
12464     return result;
12465 }
12466
12467 void
12468 GetOutOfBookInfo (char * buf)
12469 {
12470     int oob[2];
12471     int i;
12472     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12473
12474     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12475     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12476
12477     *buf = '\0';
12478
12479     if( oob[0] >= 0 || oob[1] >= 0 ) {
12480         for( i=0; i<2; i++ ) {
12481             int idx = oob[i];
12482
12483             if( idx >= 0 ) {
12484                 if( i > 0 && oob[0] >= 0 ) {
12485                     strcat( buf, "   " );
12486                 }
12487
12488                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12489                 sprintf( buf+strlen(buf), "%s%.2f",
12490                     pvInfoList[idx].score >= 0 ? "+" : "",
12491                     pvInfoList[idx].score / 100.0 );
12492             }
12493         }
12494     }
12495 }
12496
12497 /* Save game in PGN style and close the file */
12498 int
12499 SaveGamePGN (FILE *f)
12500 {
12501     int i, offset, linelen, newblock;
12502     time_t tm;
12503 //    char *movetext;
12504     char numtext[32];
12505     int movelen, numlen, blank;
12506     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12507
12508     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12509
12510     tm = time((time_t *) NULL);
12511
12512     PrintPGNTags(f, &gameInfo);
12513
12514     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12515
12516     if (backwardMostMove > 0 || startedFromSetupPosition) {
12517         char *fen = PositionToFEN(backwardMostMove, NULL);
12518         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12519         fprintf(f, "\n{--------------\n");
12520         PrintPosition(f, backwardMostMove);
12521         fprintf(f, "--------------}\n");
12522         free(fen);
12523     }
12524     else {
12525         /* [AS] Out of book annotation */
12526         if( appData.saveOutOfBookInfo ) {
12527             char buf[64];
12528
12529             GetOutOfBookInfo( buf );
12530
12531             if( buf[0] != '\0' ) {
12532                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12533             }
12534         }
12535
12536         fprintf(f, "\n");
12537     }
12538
12539     i = backwardMostMove;
12540     linelen = 0;
12541     newblock = TRUE;
12542
12543     while (i < forwardMostMove) {
12544         /* Print comments preceding this move */
12545         if (commentList[i] != NULL) {
12546             if (linelen > 0) fprintf(f, "\n");
12547             fprintf(f, "%s", commentList[i]);
12548             linelen = 0;
12549             newblock = TRUE;
12550         }
12551
12552         /* Format move number */
12553         if ((i % 2) == 0)
12554           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12555         else
12556           if (newblock)
12557             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12558           else
12559             numtext[0] = NULLCHAR;
12560
12561         numlen = strlen(numtext);
12562         newblock = FALSE;
12563
12564         /* Print move number */
12565         blank = linelen > 0 && numlen > 0;
12566         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12567             fprintf(f, "\n");
12568             linelen = 0;
12569             blank = 0;
12570         }
12571         if (blank) {
12572             fprintf(f, " ");
12573             linelen++;
12574         }
12575         fprintf(f, "%s", numtext);
12576         linelen += numlen;
12577
12578         /* Get move */
12579         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12580         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12581
12582         /* Print move */
12583         blank = linelen > 0 && movelen > 0;
12584         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12585             fprintf(f, "\n");
12586             linelen = 0;
12587             blank = 0;
12588         }
12589         if (blank) {
12590             fprintf(f, " ");
12591             linelen++;
12592         }
12593         fprintf(f, "%s", move_buffer);
12594         linelen += movelen;
12595
12596         /* [AS] Add PV info if present */
12597         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12598             /* [HGM] add time */
12599             char buf[MSG_SIZ]; int seconds;
12600
12601             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12602
12603             if( seconds <= 0)
12604               buf[0] = 0;
12605             else
12606               if( seconds < 30 )
12607                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12608               else
12609                 {
12610                   seconds = (seconds + 4)/10; // round to full seconds
12611                   if( seconds < 60 )
12612                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12613                   else
12614                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12615                 }
12616
12617             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12618                       pvInfoList[i].score >= 0 ? "+" : "",
12619                       pvInfoList[i].score / 100.0,
12620                       pvInfoList[i].depth,
12621                       buf );
12622
12623             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12624
12625             /* Print score/depth */
12626             blank = linelen > 0 && movelen > 0;
12627             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12628                 fprintf(f, "\n");
12629                 linelen = 0;
12630                 blank = 0;
12631             }
12632             if (blank) {
12633                 fprintf(f, " ");
12634                 linelen++;
12635             }
12636             fprintf(f, "%s", move_buffer);
12637             linelen += movelen;
12638         }
12639
12640         i++;
12641     }
12642
12643     /* Start a new line */
12644     if (linelen > 0) fprintf(f, "\n");
12645
12646     /* Print comments after last move */
12647     if (commentList[i] != NULL) {
12648         fprintf(f, "%s\n", commentList[i]);
12649     }
12650
12651     /* Print result */
12652     if (gameInfo.resultDetails != NULL &&
12653         gameInfo.resultDetails[0] != NULLCHAR) {
12654         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12655                 PGNResult(gameInfo.result));
12656     } else {
12657         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12658     }
12659
12660     fclose(f);
12661     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12662     return TRUE;
12663 }
12664
12665 /* Save game in old style and close the file */
12666 int
12667 SaveGameOldStyle (FILE *f)
12668 {
12669     int i, offset;
12670     time_t tm;
12671
12672     tm = time((time_t *) NULL);
12673
12674     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12675     PrintOpponents(f);
12676
12677     if (backwardMostMove > 0 || startedFromSetupPosition) {
12678         fprintf(f, "\n[--------------\n");
12679         PrintPosition(f, backwardMostMove);
12680         fprintf(f, "--------------]\n");
12681     } else {
12682         fprintf(f, "\n");
12683     }
12684
12685     i = backwardMostMove;
12686     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12687
12688     while (i < forwardMostMove) {
12689         if (commentList[i] != NULL) {
12690             fprintf(f, "[%s]\n", commentList[i]);
12691         }
12692
12693         if ((i % 2) == 1) {
12694             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12695             i++;
12696         } else {
12697             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12698             i++;
12699             if (commentList[i] != NULL) {
12700                 fprintf(f, "\n");
12701                 continue;
12702             }
12703             if (i >= forwardMostMove) {
12704                 fprintf(f, "\n");
12705                 break;
12706             }
12707             fprintf(f, "%s\n", parseList[i]);
12708             i++;
12709         }
12710     }
12711
12712     if (commentList[i] != NULL) {
12713         fprintf(f, "[%s]\n", commentList[i]);
12714     }
12715
12716     /* This isn't really the old style, but it's close enough */
12717     if (gameInfo.resultDetails != NULL &&
12718         gameInfo.resultDetails[0] != NULLCHAR) {
12719         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12720                 gameInfo.resultDetails);
12721     } else {
12722         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12723     }
12724
12725     fclose(f);
12726     return TRUE;
12727 }
12728
12729 /* Save the current game to open file f and close the file */
12730 int
12731 SaveGame (FILE *f, int dummy, char *dummy2)
12732 {
12733     if (gameMode == EditPosition) EditPositionDone(TRUE);
12734     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12735     if (appData.oldSaveStyle)
12736       return SaveGameOldStyle(f);
12737     else
12738       return SaveGamePGN(f);
12739 }
12740
12741 /* Save the current position to the given file */
12742 int
12743 SavePositionToFile (char *filename)
12744 {
12745     FILE *f;
12746     char buf[MSG_SIZ];
12747
12748     if (strcmp(filename, "-") == 0) {
12749         return SavePosition(stdout, 0, NULL);
12750     } else {
12751         f = fopen(filename, "a");
12752         if (f == NULL) {
12753             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12754             DisplayError(buf, errno);
12755             return FALSE;
12756         } else {
12757             safeStrCpy(buf, lastMsg, MSG_SIZ);
12758             DisplayMessage(_("Waiting for access to save file"), "");
12759             flock(fileno(f), LOCK_EX); // [HGM] lock
12760             DisplayMessage(_("Saving position"), "");
12761             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12762             SavePosition(f, 0, NULL);
12763             DisplayMessage(buf, "");
12764             return TRUE;
12765         }
12766     }
12767 }
12768
12769 /* Save the current position to the given open file and close the file */
12770 int
12771 SavePosition (FILE *f, int dummy, char *dummy2)
12772 {
12773     time_t tm;
12774     char *fen;
12775
12776     if (gameMode == EditPosition) EditPositionDone(TRUE);
12777     if (appData.oldSaveStyle) {
12778         tm = time((time_t *) NULL);
12779
12780         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12781         PrintOpponents(f);
12782         fprintf(f, "[--------------\n");
12783         PrintPosition(f, currentMove);
12784         fprintf(f, "--------------]\n");
12785     } else {
12786         fen = PositionToFEN(currentMove, NULL);
12787         fprintf(f, "%s\n", fen);
12788         free(fen);
12789     }
12790     fclose(f);
12791     return TRUE;
12792 }
12793
12794 void
12795 ReloadCmailMsgEvent (int unregister)
12796 {
12797 #if !WIN32
12798     static char *inFilename = NULL;
12799     static char *outFilename;
12800     int i;
12801     struct stat inbuf, outbuf;
12802     int status;
12803
12804     /* Any registered moves are unregistered if unregister is set, */
12805     /* i.e. invoked by the signal handler */
12806     if (unregister) {
12807         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12808             cmailMoveRegistered[i] = FALSE;
12809             if (cmailCommentList[i] != NULL) {
12810                 free(cmailCommentList[i]);
12811                 cmailCommentList[i] = NULL;
12812             }
12813         }
12814         nCmailMovesRegistered = 0;
12815     }
12816
12817     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12818         cmailResult[i] = CMAIL_NOT_RESULT;
12819     }
12820     nCmailResults = 0;
12821
12822     if (inFilename == NULL) {
12823         /* Because the filenames are static they only get malloced once  */
12824         /* and they never get freed                                      */
12825         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12826         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12827
12828         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12829         sprintf(outFilename, "%s.out", appData.cmailGameName);
12830     }
12831
12832     status = stat(outFilename, &outbuf);
12833     if (status < 0) {
12834         cmailMailedMove = FALSE;
12835     } else {
12836         status = stat(inFilename, &inbuf);
12837         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12838     }
12839
12840     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12841        counts the games, notes how each one terminated, etc.
12842
12843        It would be nice to remove this kludge and instead gather all
12844        the information while building the game list.  (And to keep it
12845        in the game list nodes instead of having a bunch of fixed-size
12846        parallel arrays.)  Note this will require getting each game's
12847        termination from the PGN tags, as the game list builder does
12848        not process the game moves.  --mann
12849        */
12850     cmailMsgLoaded = TRUE;
12851     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12852
12853     /* Load first game in the file or popup game menu */
12854     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12855
12856 #endif /* !WIN32 */
12857     return;
12858 }
12859
12860 int
12861 RegisterMove ()
12862 {
12863     FILE *f;
12864     char string[MSG_SIZ];
12865
12866     if (   cmailMailedMove
12867         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12868         return TRUE;            /* Allow free viewing  */
12869     }
12870
12871     /* Unregister move to ensure that we don't leave RegisterMove        */
12872     /* with the move registered when the conditions for registering no   */
12873     /* longer hold                                                       */
12874     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12875         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12876         nCmailMovesRegistered --;
12877
12878         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12879           {
12880               free(cmailCommentList[lastLoadGameNumber - 1]);
12881               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12882           }
12883     }
12884
12885     if (cmailOldMove == -1) {
12886         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12887         return FALSE;
12888     }
12889
12890     if (currentMove > cmailOldMove + 1) {
12891         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12892         return FALSE;
12893     }
12894
12895     if (currentMove < cmailOldMove) {
12896         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12897         return FALSE;
12898     }
12899
12900     if (forwardMostMove > currentMove) {
12901         /* Silently truncate extra moves */
12902         TruncateGame();
12903     }
12904
12905     if (   (currentMove == cmailOldMove + 1)
12906         || (   (currentMove == cmailOldMove)
12907             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12908                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12909         if (gameInfo.result != GameUnfinished) {
12910             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12911         }
12912
12913         if (commentList[currentMove] != NULL) {
12914             cmailCommentList[lastLoadGameNumber - 1]
12915               = StrSave(commentList[currentMove]);
12916         }
12917         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12918
12919         if (appData.debugMode)
12920           fprintf(debugFP, "Saving %s for game %d\n",
12921                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12922
12923         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12924
12925         f = fopen(string, "w");
12926         if (appData.oldSaveStyle) {
12927             SaveGameOldStyle(f); /* also closes the file */
12928
12929             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12930             f = fopen(string, "w");
12931             SavePosition(f, 0, NULL); /* also closes the file */
12932         } else {
12933             fprintf(f, "{--------------\n");
12934             PrintPosition(f, currentMove);
12935             fprintf(f, "--------------}\n\n");
12936
12937             SaveGame(f, 0, NULL); /* also closes the file*/
12938         }
12939
12940         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12941         nCmailMovesRegistered ++;
12942     } else if (nCmailGames == 1) {
12943         DisplayError(_("You have not made a move yet"), 0);
12944         return FALSE;
12945     }
12946
12947     return TRUE;
12948 }
12949
12950 void
12951 MailMoveEvent ()
12952 {
12953 #if !WIN32
12954     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12955     FILE *commandOutput;
12956     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12957     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12958     int nBuffers;
12959     int i;
12960     int archived;
12961     char *arcDir;
12962
12963     if (! cmailMsgLoaded) {
12964         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12965         return;
12966     }
12967
12968     if (nCmailGames == nCmailResults) {
12969         DisplayError(_("No unfinished games"), 0);
12970         return;
12971     }
12972
12973 #if CMAIL_PROHIBIT_REMAIL
12974     if (cmailMailedMove) {
12975       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);
12976         DisplayError(msg, 0);
12977         return;
12978     }
12979 #endif
12980
12981     if (! (cmailMailedMove || RegisterMove())) return;
12982
12983     if (   cmailMailedMove
12984         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12985       snprintf(string, MSG_SIZ, partCommandString,
12986                appData.debugMode ? " -v" : "", appData.cmailGameName);
12987         commandOutput = popen(string, "r");
12988
12989         if (commandOutput == NULL) {
12990             DisplayError(_("Failed to invoke cmail"), 0);
12991         } else {
12992             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12993                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12994             }
12995             if (nBuffers > 1) {
12996                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12997                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12998                 nBytes = MSG_SIZ - 1;
12999             } else {
13000                 (void) memcpy(msg, buffer, nBytes);
13001             }
13002             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13003
13004             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13005                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13006
13007                 archived = TRUE;
13008                 for (i = 0; i < nCmailGames; i ++) {
13009                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13010                         archived = FALSE;
13011                     }
13012                 }
13013                 if (   archived
13014                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13015                         != NULL)) {
13016                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13017                            arcDir,
13018                            appData.cmailGameName,
13019                            gameInfo.date);
13020                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13021                     cmailMsgLoaded = FALSE;
13022                 }
13023             }
13024
13025             DisplayInformation(msg);
13026             pclose(commandOutput);
13027         }
13028     } else {
13029         if ((*cmailMsg) != '\0') {
13030             DisplayInformation(cmailMsg);
13031         }
13032     }
13033
13034     return;
13035 #endif /* !WIN32 */
13036 }
13037
13038 char *
13039 CmailMsg ()
13040 {
13041 #if WIN32
13042     return NULL;
13043 #else
13044     int  prependComma = 0;
13045     char number[5];
13046     char string[MSG_SIZ];       /* Space for game-list */
13047     int  i;
13048
13049     if (!cmailMsgLoaded) return "";
13050
13051     if (cmailMailedMove) {
13052       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13053     } else {
13054         /* Create a list of games left */
13055       snprintf(string, MSG_SIZ, "[");
13056         for (i = 0; i < nCmailGames; i ++) {
13057             if (! (   cmailMoveRegistered[i]
13058                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13059                 if (prependComma) {
13060                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13061                 } else {
13062                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13063                     prependComma = 1;
13064                 }
13065
13066                 strcat(string, number);
13067             }
13068         }
13069         strcat(string, "]");
13070
13071         if (nCmailMovesRegistered + nCmailResults == 0) {
13072             switch (nCmailGames) {
13073               case 1:
13074                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13075                 break;
13076
13077               case 2:
13078                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13079                 break;
13080
13081               default:
13082                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13083                          nCmailGames);
13084                 break;
13085             }
13086         } else {
13087             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13088               case 1:
13089                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13090                          string);
13091                 break;
13092
13093               case 0:
13094                 if (nCmailResults == nCmailGames) {
13095                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13096                 } else {
13097                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13098                 }
13099                 break;
13100
13101               default:
13102                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13103                          string);
13104             }
13105         }
13106     }
13107     return cmailMsg;
13108 #endif /* WIN32 */
13109 }
13110
13111 void
13112 ResetGameEvent ()
13113 {
13114     if (gameMode == Training)
13115       SetTrainingModeOff();
13116
13117     Reset(TRUE, TRUE);
13118     cmailMsgLoaded = FALSE;
13119     if (appData.icsActive) {
13120       SendToICS(ics_prefix);
13121       SendToICS("refresh\n");
13122     }
13123 }
13124
13125 void
13126 ExitEvent (int status)
13127 {
13128     exiting++;
13129     if (exiting > 2) {
13130       /* Give up on clean exit */
13131       exit(status);
13132     }
13133     if (exiting > 1) {
13134       /* Keep trying for clean exit */
13135       return;
13136     }
13137
13138     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13139
13140     if (telnetISR != NULL) {
13141       RemoveInputSource(telnetISR);
13142     }
13143     if (icsPR != NoProc) {
13144       DestroyChildProcess(icsPR, TRUE);
13145     }
13146
13147     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13148     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13149
13150     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13151     /* make sure this other one finishes before killing it!                  */
13152     if(endingGame) { int count = 0;
13153         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13154         while(endingGame && count++ < 10) DoSleep(1);
13155         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13156     }
13157
13158     /* Kill off chess programs */
13159     if (first.pr != NoProc) {
13160         ExitAnalyzeMode();
13161
13162         DoSleep( appData.delayBeforeQuit );
13163         SendToProgram("quit\n", &first);
13164         DoSleep( appData.delayAfterQuit );
13165         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13166     }
13167     if (second.pr != NoProc) {
13168         DoSleep( appData.delayBeforeQuit );
13169         SendToProgram("quit\n", &second);
13170         DoSleep( appData.delayAfterQuit );
13171         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13172     }
13173     if (first.isr != NULL) {
13174         RemoveInputSource(first.isr);
13175     }
13176     if (second.isr != NULL) {
13177         RemoveInputSource(second.isr);
13178     }
13179
13180     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13181     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13182
13183     ShutDownFrontEnd();
13184     exit(status);
13185 }
13186
13187 void
13188 PauseEvent ()
13189 {
13190     if (appData.debugMode)
13191         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13192     if (pausing) {
13193         pausing = FALSE;
13194         ModeHighlight();
13195         if (gameMode == MachinePlaysWhite ||
13196             gameMode == MachinePlaysBlack) {
13197             StartClocks();
13198         } else {
13199             DisplayBothClocks();
13200         }
13201         if (gameMode == PlayFromGameFile) {
13202             if (appData.timeDelay >= 0)
13203                 AutoPlayGameLoop();
13204         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13205             Reset(FALSE, TRUE);
13206             SendToICS(ics_prefix);
13207             SendToICS("refresh\n");
13208         } else if (currentMove < forwardMostMove) {
13209             ForwardInner(forwardMostMove);
13210         }
13211         pauseExamInvalid = FALSE;
13212     } else {
13213         switch (gameMode) {
13214           default:
13215             return;
13216           case IcsExamining:
13217             pauseExamForwardMostMove = forwardMostMove;
13218             pauseExamInvalid = FALSE;
13219             /* fall through */
13220           case IcsObserving:
13221           case IcsPlayingWhite:
13222           case IcsPlayingBlack:
13223             pausing = TRUE;
13224             ModeHighlight();
13225             return;
13226           case PlayFromGameFile:
13227             (void) StopLoadGameTimer();
13228             pausing = TRUE;
13229             ModeHighlight();
13230             break;
13231           case BeginningOfGame:
13232             if (appData.icsActive) return;
13233             /* else fall through */
13234           case MachinePlaysWhite:
13235           case MachinePlaysBlack:
13236           case TwoMachinesPlay:
13237             if (forwardMostMove == 0)
13238               return;           /* don't pause if no one has moved */
13239             if ((gameMode == MachinePlaysWhite &&
13240                  !WhiteOnMove(forwardMostMove)) ||
13241                 (gameMode == MachinePlaysBlack &&
13242                  WhiteOnMove(forwardMostMove))) {
13243                 StopClocks();
13244             }
13245             pausing = TRUE;
13246             ModeHighlight();
13247             break;
13248         }
13249     }
13250 }
13251
13252 void
13253 EditCommentEvent ()
13254 {
13255     char title[MSG_SIZ];
13256
13257     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13258       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13259     } else {
13260       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13261                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13262                parseList[currentMove - 1]);
13263     }
13264
13265     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13266 }
13267
13268
13269 void
13270 EditTagsEvent ()
13271 {
13272     char *tags = PGNTags(&gameInfo);
13273     bookUp = FALSE;
13274     EditTagsPopUp(tags, NULL);
13275     free(tags);
13276 }
13277
13278 void
13279 AnalyzeModeEvent ()
13280 {
13281     if (appData.noChessProgram || gameMode == AnalyzeMode)
13282       return;
13283
13284     if (gameMode != AnalyzeFile) {
13285         if (!appData.icsEngineAnalyze) {
13286                EditGameEvent();
13287                if (gameMode != EditGame) return;
13288         }
13289         ResurrectChessProgram();
13290         SendToProgram("analyze\n", &first);
13291         first.analyzing = TRUE;
13292         /*first.maybeThinking = TRUE;*/
13293         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13294         EngineOutputPopUp();
13295     }
13296     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13297     pausing = FALSE;
13298     ModeHighlight();
13299     SetGameInfo();
13300
13301     StartAnalysisClock();
13302     GetTimeMark(&lastNodeCountTime);
13303     lastNodeCount = 0;
13304 }
13305
13306 void
13307 AnalyzeFileEvent ()
13308 {
13309     if (appData.noChessProgram || gameMode == AnalyzeFile)
13310       return;
13311
13312     if (gameMode != AnalyzeMode) {
13313         EditGameEvent();
13314         if (gameMode != EditGame) return;
13315         ResurrectChessProgram();
13316         SendToProgram("analyze\n", &first);
13317         first.analyzing = TRUE;
13318         /*first.maybeThinking = TRUE;*/
13319         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13320         EngineOutputPopUp();
13321     }
13322     gameMode = AnalyzeFile;
13323     pausing = FALSE;
13324     ModeHighlight();
13325     SetGameInfo();
13326
13327     StartAnalysisClock();
13328     GetTimeMark(&lastNodeCountTime);
13329     lastNodeCount = 0;
13330     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13331 }
13332
13333 void
13334 MachineWhiteEvent ()
13335 {
13336     char buf[MSG_SIZ];
13337     char *bookHit = NULL;
13338
13339     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13340       return;
13341
13342
13343     if (gameMode == PlayFromGameFile ||
13344         gameMode == TwoMachinesPlay  ||
13345         gameMode == Training         ||
13346         gameMode == AnalyzeMode      ||
13347         gameMode == EndOfGame)
13348         EditGameEvent();
13349
13350     if (gameMode == EditPosition)
13351         EditPositionDone(TRUE);
13352
13353     if (!WhiteOnMove(currentMove)) {
13354         DisplayError(_("It is not White's turn"), 0);
13355         return;
13356     }
13357
13358     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13359       ExitAnalyzeMode();
13360
13361     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13362         gameMode == AnalyzeFile)
13363         TruncateGame();
13364
13365     ResurrectChessProgram();    /* in case it isn't running */
13366     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13367         gameMode = MachinePlaysWhite;
13368         ResetClocks();
13369     } else
13370     gameMode = MachinePlaysWhite;
13371     pausing = FALSE;
13372     ModeHighlight();
13373     SetGameInfo();
13374     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13375     DisplayTitle(buf);
13376     if (first.sendName) {
13377       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13378       SendToProgram(buf, &first);
13379     }
13380     if (first.sendTime) {
13381       if (first.useColors) {
13382         SendToProgram("black\n", &first); /*gnu kludge*/
13383       }
13384       SendTimeRemaining(&first, TRUE);
13385     }
13386     if (first.useColors) {
13387       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13388     }
13389     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13390     SetMachineThinkingEnables();
13391     first.maybeThinking = TRUE;
13392     StartClocks();
13393     firstMove = FALSE;
13394
13395     if (appData.autoFlipView && !flipView) {
13396       flipView = !flipView;
13397       DrawPosition(FALSE, NULL);
13398       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13399     }
13400
13401     if(bookHit) { // [HGM] book: simulate book reply
13402         static char bookMove[MSG_SIZ]; // a bit generous?
13403
13404         programStats.nodes = programStats.depth = programStats.time =
13405         programStats.score = programStats.got_only_move = 0;
13406         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13407
13408         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13409         strcat(bookMove, bookHit);
13410         HandleMachineMove(bookMove, &first);
13411     }
13412 }
13413
13414 void
13415 MachineBlackEvent ()
13416 {
13417   char buf[MSG_SIZ];
13418   char *bookHit = NULL;
13419
13420     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13421         return;
13422
13423
13424     if (gameMode == PlayFromGameFile ||
13425         gameMode == TwoMachinesPlay  ||
13426         gameMode == Training         ||
13427         gameMode == AnalyzeMode      ||
13428         gameMode == EndOfGame)
13429         EditGameEvent();
13430
13431     if (gameMode == EditPosition)
13432         EditPositionDone(TRUE);
13433
13434     if (WhiteOnMove(currentMove)) {
13435         DisplayError(_("It is not Black's turn"), 0);
13436         return;
13437     }
13438
13439     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13440       ExitAnalyzeMode();
13441
13442     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13443         gameMode == AnalyzeFile)
13444         TruncateGame();
13445
13446     ResurrectChessProgram();    /* in case it isn't running */
13447     gameMode = MachinePlaysBlack;
13448     pausing = FALSE;
13449     ModeHighlight();
13450     SetGameInfo();
13451     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13452     DisplayTitle(buf);
13453     if (first.sendName) {
13454       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13455       SendToProgram(buf, &first);
13456     }
13457     if (first.sendTime) {
13458       if (first.useColors) {
13459         SendToProgram("white\n", &first); /*gnu kludge*/
13460       }
13461       SendTimeRemaining(&first, FALSE);
13462     }
13463     if (first.useColors) {
13464       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13465     }
13466     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13467     SetMachineThinkingEnables();
13468     first.maybeThinking = TRUE;
13469     StartClocks();
13470
13471     if (appData.autoFlipView && flipView) {
13472       flipView = !flipView;
13473       DrawPosition(FALSE, NULL);
13474       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13475     }
13476     if(bookHit) { // [HGM] book: simulate book reply
13477         static char bookMove[MSG_SIZ]; // a bit generous?
13478
13479         programStats.nodes = programStats.depth = programStats.time =
13480         programStats.score = programStats.got_only_move = 0;
13481         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13482
13483         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13484         strcat(bookMove, bookHit);
13485         HandleMachineMove(bookMove, &first);
13486     }
13487 }
13488
13489
13490 void
13491 DisplayTwoMachinesTitle ()
13492 {
13493     char buf[MSG_SIZ];
13494     if (appData.matchGames > 0) {
13495         if(appData.tourneyFile[0]) {
13496           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13497                    gameInfo.white, _("vs."), gameInfo.black,
13498                    nextGame+1, appData.matchGames+1,
13499                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13500         } else 
13501         if (first.twoMachinesColor[0] == 'w') {
13502           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13503                    gameInfo.white, _("vs."),  gameInfo.black,
13504                    first.matchWins, second.matchWins,
13505                    matchGame - 1 - (first.matchWins + second.matchWins));
13506         } else {
13507           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13508                    gameInfo.white, _("vs."), gameInfo.black,
13509                    second.matchWins, first.matchWins,
13510                    matchGame - 1 - (first.matchWins + second.matchWins));
13511         }
13512     } else {
13513       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13514     }
13515     DisplayTitle(buf);
13516 }
13517
13518 void
13519 SettingsMenuIfReady ()
13520 {
13521   if (second.lastPing != second.lastPong) {
13522     DisplayMessage("", _("Waiting for second chess program"));
13523     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13524     return;
13525   }
13526   ThawUI();
13527   DisplayMessage("", "");
13528   SettingsPopUp(&second);
13529 }
13530
13531 int
13532 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13533 {
13534     char buf[MSG_SIZ];
13535     if (cps->pr == NoProc) {
13536         StartChessProgram(cps);
13537         if (cps->protocolVersion == 1) {
13538           retry();
13539         } else {
13540           /* kludge: allow timeout for initial "feature" command */
13541           FreezeUI();
13542           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13543           DisplayMessage("", buf);
13544           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13545         }
13546         return 1;
13547     }
13548     return 0;
13549 }
13550
13551 void
13552 TwoMachinesEvent P((void))
13553 {
13554     int i;
13555     char buf[MSG_SIZ];
13556     ChessProgramState *onmove;
13557     char *bookHit = NULL;
13558     static int stalling = 0;
13559     TimeMark now;
13560     long wait;
13561
13562     if (appData.noChessProgram) return;
13563
13564     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13565         DisplayError("second engine does not play this", 0);
13566         return;
13567     }
13568
13569     switch (gameMode) {
13570       case TwoMachinesPlay:
13571         return;
13572       case MachinePlaysWhite:
13573       case MachinePlaysBlack:
13574         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13575             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13576             return;
13577         }
13578         /* fall through */
13579       case BeginningOfGame:
13580       case PlayFromGameFile:
13581       case EndOfGame:
13582         EditGameEvent();
13583         if (gameMode != EditGame) return;
13584         break;
13585       case EditPosition:
13586         EditPositionDone(TRUE);
13587         break;
13588       case AnalyzeMode:
13589       case AnalyzeFile:
13590         ExitAnalyzeMode();
13591         break;
13592       case EditGame:
13593       default:
13594         break;
13595     }
13596
13597 //    forwardMostMove = currentMove;
13598     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13599
13600     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13601
13602     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13603     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13604       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13605       return;
13606     }
13607     if(!stalling) {
13608       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13609       SendToProgram("force\n", &second);
13610       stalling = 1;
13611       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13612       return;
13613     }
13614     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13615     if(appData.matchPause>10000 || appData.matchPause<10)
13616                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13617     wait = SubtractTimeMarks(&now, &pauseStart);
13618     if(wait < appData.matchPause) {
13619         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13620         return;
13621     }
13622     // we are now committed to starting the game
13623     stalling = 0;
13624     DisplayMessage("", "");
13625     if (startedFromSetupPosition) {
13626         SendBoard(&second, backwardMostMove);
13627     if (appData.debugMode) {
13628         fprintf(debugFP, "Two Machines\n");
13629     }
13630     }
13631     for (i = backwardMostMove; i < forwardMostMove; i++) {
13632         SendMoveToProgram(i, &second);
13633     }
13634
13635     gameMode = TwoMachinesPlay;
13636     pausing = FALSE;
13637     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13638     SetGameInfo();
13639     DisplayTwoMachinesTitle();
13640     firstMove = TRUE;
13641     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13642         onmove = &first;
13643     } else {
13644         onmove = &second;
13645     }
13646     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13647     SendToProgram(first.computerString, &first);
13648     if (first.sendName) {
13649       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13650       SendToProgram(buf, &first);
13651     }
13652     SendToProgram(second.computerString, &second);
13653     if (second.sendName) {
13654       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13655       SendToProgram(buf, &second);
13656     }
13657
13658     ResetClocks();
13659     if (!first.sendTime || !second.sendTime) {
13660         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13661         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13662     }
13663     if (onmove->sendTime) {
13664       if (onmove->useColors) {
13665         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13666       }
13667       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13668     }
13669     if (onmove->useColors) {
13670       SendToProgram(onmove->twoMachinesColor, onmove);
13671     }
13672     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13673 //    SendToProgram("go\n", onmove);
13674     onmove->maybeThinking = TRUE;
13675     SetMachineThinkingEnables();
13676
13677     StartClocks();
13678
13679     if(bookHit) { // [HGM] book: simulate book reply
13680         static char bookMove[MSG_SIZ]; // a bit generous?
13681
13682         programStats.nodes = programStats.depth = programStats.time =
13683         programStats.score = programStats.got_only_move = 0;
13684         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13685
13686         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13687         strcat(bookMove, bookHit);
13688         savedMessage = bookMove; // args for deferred call
13689         savedState = onmove;
13690         ScheduleDelayedEvent(DeferredBookMove, 1);
13691     }
13692 }
13693
13694 void
13695 TrainingEvent ()
13696 {
13697     if (gameMode == Training) {
13698       SetTrainingModeOff();
13699       gameMode = PlayFromGameFile;
13700       DisplayMessage("", _("Training mode off"));
13701     } else {
13702       gameMode = Training;
13703       animateTraining = appData.animate;
13704
13705       /* make sure we are not already at the end of the game */
13706       if (currentMove < forwardMostMove) {
13707         SetTrainingModeOn();
13708         DisplayMessage("", _("Training mode on"));
13709       } else {
13710         gameMode = PlayFromGameFile;
13711         DisplayError(_("Already at end of game"), 0);
13712       }
13713     }
13714     ModeHighlight();
13715 }
13716
13717 void
13718 IcsClientEvent ()
13719 {
13720     if (!appData.icsActive) return;
13721     switch (gameMode) {
13722       case IcsPlayingWhite:
13723       case IcsPlayingBlack:
13724       case IcsObserving:
13725       case IcsIdle:
13726       case BeginningOfGame:
13727       case IcsExamining:
13728         return;
13729
13730       case EditGame:
13731         break;
13732
13733       case EditPosition:
13734         EditPositionDone(TRUE);
13735         break;
13736
13737       case AnalyzeMode:
13738       case AnalyzeFile:
13739         ExitAnalyzeMode();
13740         break;
13741
13742       default:
13743         EditGameEvent();
13744         break;
13745     }
13746
13747     gameMode = IcsIdle;
13748     ModeHighlight();
13749     return;
13750 }
13751
13752 void
13753 EditGameEvent ()
13754 {
13755     int i;
13756
13757     switch (gameMode) {
13758       case Training:
13759         SetTrainingModeOff();
13760         break;
13761       case MachinePlaysWhite:
13762       case MachinePlaysBlack:
13763       case BeginningOfGame:
13764         SendToProgram("force\n", &first);
13765         SetUserThinkingEnables();
13766         break;
13767       case PlayFromGameFile:
13768         (void) StopLoadGameTimer();
13769         if (gameFileFP != NULL) {
13770             gameFileFP = NULL;
13771         }
13772         break;
13773       case EditPosition:
13774         EditPositionDone(TRUE);
13775         break;
13776       case AnalyzeMode:
13777       case AnalyzeFile:
13778         ExitAnalyzeMode();
13779         SendToProgram("force\n", &first);
13780         break;
13781       case TwoMachinesPlay:
13782         GameEnds(EndOfFile, NULL, GE_PLAYER);
13783         ResurrectChessProgram();
13784         SetUserThinkingEnables();
13785         break;
13786       case EndOfGame:
13787         ResurrectChessProgram();
13788         break;
13789       case IcsPlayingBlack:
13790       case IcsPlayingWhite:
13791         DisplayError(_("Warning: You are still playing a game"), 0);
13792         break;
13793       case IcsObserving:
13794         DisplayError(_("Warning: You are still observing a game"), 0);
13795         break;
13796       case IcsExamining:
13797         DisplayError(_("Warning: You are still examining a game"), 0);
13798         break;
13799       case IcsIdle:
13800         break;
13801       case EditGame:
13802       default:
13803         return;
13804     }
13805
13806     pausing = FALSE;
13807     StopClocks();
13808     first.offeredDraw = second.offeredDraw = 0;
13809
13810     if (gameMode == PlayFromGameFile) {
13811         whiteTimeRemaining = timeRemaining[0][currentMove];
13812         blackTimeRemaining = timeRemaining[1][currentMove];
13813         DisplayTitle("");
13814     }
13815
13816     if (gameMode == MachinePlaysWhite ||
13817         gameMode == MachinePlaysBlack ||
13818         gameMode == TwoMachinesPlay ||
13819         gameMode == EndOfGame) {
13820         i = forwardMostMove;
13821         while (i > currentMove) {
13822             SendToProgram("undo\n", &first);
13823             i--;
13824         }
13825         if(!adjustedClock) {
13826         whiteTimeRemaining = timeRemaining[0][currentMove];
13827         blackTimeRemaining = timeRemaining[1][currentMove];
13828         DisplayBothClocks();
13829         }
13830         if (whiteFlag || blackFlag) {
13831             whiteFlag = blackFlag = 0;
13832         }
13833         DisplayTitle("");
13834     }
13835
13836     gameMode = EditGame;
13837     ModeHighlight();
13838     SetGameInfo();
13839 }
13840
13841
13842 void
13843 EditPositionEvent ()
13844 {
13845     if (gameMode == EditPosition) {
13846         EditGameEvent();
13847         return;
13848     }
13849
13850     EditGameEvent();
13851     if (gameMode != EditGame) return;
13852
13853     gameMode = EditPosition;
13854     ModeHighlight();
13855     SetGameInfo();
13856     if (currentMove > 0)
13857       CopyBoard(boards[0], boards[currentMove]);
13858
13859     blackPlaysFirst = !WhiteOnMove(currentMove);
13860     ResetClocks();
13861     currentMove = forwardMostMove = backwardMostMove = 0;
13862     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13863     DisplayMove(-1);
13864     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13865 }
13866
13867 void
13868 ExitAnalyzeMode ()
13869 {
13870     /* [DM] icsEngineAnalyze - possible call from other functions */
13871     if (appData.icsEngineAnalyze) {
13872         appData.icsEngineAnalyze = FALSE;
13873
13874         DisplayMessage("",_("Close ICS engine analyze..."));
13875     }
13876     if (first.analysisSupport && first.analyzing) {
13877       SendToProgram("exit\n", &first);
13878       first.analyzing = FALSE;
13879     }
13880     thinkOutput[0] = NULLCHAR;
13881 }
13882
13883 void
13884 EditPositionDone (Boolean fakeRights)
13885 {
13886     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13887
13888     startedFromSetupPosition = TRUE;
13889     InitChessProgram(&first, FALSE);
13890     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13891       boards[0][EP_STATUS] = EP_NONE;
13892       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13893     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13894         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13895         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13896       } else boards[0][CASTLING][2] = NoRights;
13897     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13898         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13899         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13900       } else boards[0][CASTLING][5] = NoRights;
13901     }
13902     SendToProgram("force\n", &first);
13903     if (blackPlaysFirst) {
13904         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13905         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13906         currentMove = forwardMostMove = backwardMostMove = 1;
13907         CopyBoard(boards[1], boards[0]);
13908     } else {
13909         currentMove = forwardMostMove = backwardMostMove = 0;
13910     }
13911     SendBoard(&first, forwardMostMove);
13912     if (appData.debugMode) {
13913         fprintf(debugFP, "EditPosDone\n");
13914     }
13915     DisplayTitle("");
13916     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13917     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13918     gameMode = EditGame;
13919     ModeHighlight();
13920     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13921     ClearHighlights(); /* [AS] */
13922 }
13923
13924 /* Pause for `ms' milliseconds */
13925 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13926 void
13927 TimeDelay (long ms)
13928 {
13929     TimeMark m1, m2;
13930
13931     GetTimeMark(&m1);
13932     do {
13933         GetTimeMark(&m2);
13934     } while (SubtractTimeMarks(&m2, &m1) < ms);
13935 }
13936
13937 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13938 void
13939 SendMultiLineToICS (char *buf)
13940 {
13941     char temp[MSG_SIZ+1], *p;
13942     int len;
13943
13944     len = strlen(buf);
13945     if (len > MSG_SIZ)
13946       len = MSG_SIZ;
13947
13948     strncpy(temp, buf, len);
13949     temp[len] = 0;
13950
13951     p = temp;
13952     while (*p) {
13953         if (*p == '\n' || *p == '\r')
13954           *p = ' ';
13955         ++p;
13956     }
13957
13958     strcat(temp, "\n");
13959     SendToICS(temp);
13960     SendToPlayer(temp, strlen(temp));
13961 }
13962
13963 void
13964 SetWhiteToPlayEvent ()
13965 {
13966     if (gameMode == EditPosition) {
13967         blackPlaysFirst = FALSE;
13968         DisplayBothClocks();    /* works because currentMove is 0 */
13969     } else if (gameMode == IcsExamining) {
13970         SendToICS(ics_prefix);
13971         SendToICS("tomove white\n");
13972     }
13973 }
13974
13975 void
13976 SetBlackToPlayEvent ()
13977 {
13978     if (gameMode == EditPosition) {
13979         blackPlaysFirst = TRUE;
13980         currentMove = 1;        /* kludge */
13981         DisplayBothClocks();
13982         currentMove = 0;
13983     } else if (gameMode == IcsExamining) {
13984         SendToICS(ics_prefix);
13985         SendToICS("tomove black\n");
13986     }
13987 }
13988
13989 void
13990 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13991 {
13992     char buf[MSG_SIZ];
13993     ChessSquare piece = boards[0][y][x];
13994
13995     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13996
13997     switch (selection) {
13998       case ClearBoard:
13999         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14000             SendToICS(ics_prefix);
14001             SendToICS("bsetup clear\n");
14002         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14003             SendToICS(ics_prefix);
14004             SendToICS("clearboard\n");
14005         } else {
14006             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14007                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14008                 for (y = 0; y < BOARD_HEIGHT; y++) {
14009                     if (gameMode == IcsExamining) {
14010                         if (boards[currentMove][y][x] != EmptySquare) {
14011                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14012                                     AAA + x, ONE + y);
14013                             SendToICS(buf);
14014                         }
14015                     } else {
14016                         boards[0][y][x] = p;
14017                     }
14018                 }
14019             }
14020         }
14021         if (gameMode == EditPosition) {
14022             DrawPosition(FALSE, boards[0]);
14023         }
14024         break;
14025
14026       case WhitePlay:
14027         SetWhiteToPlayEvent();
14028         break;
14029
14030       case BlackPlay:
14031         SetBlackToPlayEvent();
14032         break;
14033
14034       case EmptySquare:
14035         if (gameMode == IcsExamining) {
14036             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14037             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14038             SendToICS(buf);
14039         } else {
14040             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14041                 if(x == BOARD_LEFT-2) {
14042                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14043                     boards[0][y][1] = 0;
14044                 } else
14045                 if(x == BOARD_RGHT+1) {
14046                     if(y >= gameInfo.holdingsSize) break;
14047                     boards[0][y][BOARD_WIDTH-2] = 0;
14048                 } else break;
14049             }
14050             boards[0][y][x] = EmptySquare;
14051             DrawPosition(FALSE, boards[0]);
14052         }
14053         break;
14054
14055       case PromotePiece:
14056         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14057            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14058             selection = (ChessSquare) (PROMOTED piece);
14059         } else if(piece == EmptySquare) selection = WhiteSilver;
14060         else selection = (ChessSquare)((int)piece - 1);
14061         goto defaultlabel;
14062
14063       case DemotePiece:
14064         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14065            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14066             selection = (ChessSquare) (DEMOTED piece);
14067         } else if(piece == EmptySquare) selection = BlackSilver;
14068         else selection = (ChessSquare)((int)piece + 1);
14069         goto defaultlabel;
14070
14071       case WhiteQueen:
14072       case BlackQueen:
14073         if(gameInfo.variant == VariantShatranj ||
14074            gameInfo.variant == VariantXiangqi  ||
14075            gameInfo.variant == VariantCourier  ||
14076            gameInfo.variant == VariantMakruk     )
14077             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14078         goto defaultlabel;
14079
14080       case WhiteKing:
14081       case BlackKing:
14082         if(gameInfo.variant == VariantXiangqi)
14083             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14084         if(gameInfo.variant == VariantKnightmate)
14085             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14086       default:
14087         defaultlabel:
14088         if (gameMode == IcsExamining) {
14089             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14090             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14091                      PieceToChar(selection), AAA + x, ONE + y);
14092             SendToICS(buf);
14093         } else {
14094             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14095                 int n;
14096                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14097                     n = PieceToNumber(selection - BlackPawn);
14098                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14099                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14100                     boards[0][BOARD_HEIGHT-1-n][1]++;
14101                 } else
14102                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14103                     n = PieceToNumber(selection);
14104                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14105                     boards[0][n][BOARD_WIDTH-1] = selection;
14106                     boards[0][n][BOARD_WIDTH-2]++;
14107                 }
14108             } else
14109             boards[0][y][x] = selection;
14110             DrawPosition(TRUE, boards[0]);
14111             ClearHighlights();
14112             fromX = fromY = -1;
14113         }
14114         break;
14115     }
14116 }
14117
14118
14119 void
14120 DropMenuEvent (ChessSquare selection, int x, int y)
14121 {
14122     ChessMove moveType;
14123
14124     switch (gameMode) {
14125       case IcsPlayingWhite:
14126       case MachinePlaysBlack:
14127         if (!WhiteOnMove(currentMove)) {
14128             DisplayMoveError(_("It is Black's turn"));
14129             return;
14130         }
14131         moveType = WhiteDrop;
14132         break;
14133       case IcsPlayingBlack:
14134       case MachinePlaysWhite:
14135         if (WhiteOnMove(currentMove)) {
14136             DisplayMoveError(_("It is White's turn"));
14137             return;
14138         }
14139         moveType = BlackDrop;
14140         break;
14141       case EditGame:
14142         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14143         break;
14144       default:
14145         return;
14146     }
14147
14148     if (moveType == BlackDrop && selection < BlackPawn) {
14149       selection = (ChessSquare) ((int) selection
14150                                  + (int) BlackPawn - (int) WhitePawn);
14151     }
14152     if (boards[currentMove][y][x] != EmptySquare) {
14153         DisplayMoveError(_("That square is occupied"));
14154         return;
14155     }
14156
14157     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14158 }
14159
14160 void
14161 AcceptEvent ()
14162 {
14163     /* Accept a pending offer of any kind from opponent */
14164
14165     if (appData.icsActive) {
14166         SendToICS(ics_prefix);
14167         SendToICS("accept\n");
14168     } else if (cmailMsgLoaded) {
14169         if (currentMove == cmailOldMove &&
14170             commentList[cmailOldMove] != NULL &&
14171             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14172                    "Black offers a draw" : "White offers a draw")) {
14173             TruncateGame();
14174             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14175             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14176         } else {
14177             DisplayError(_("There is no pending offer on this move"), 0);
14178             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14179         }
14180     } else {
14181         /* Not used for offers from chess program */
14182     }
14183 }
14184
14185 void
14186 DeclineEvent ()
14187 {
14188     /* Decline a pending offer of any kind from opponent */
14189
14190     if (appData.icsActive) {
14191         SendToICS(ics_prefix);
14192         SendToICS("decline\n");
14193     } else if (cmailMsgLoaded) {
14194         if (currentMove == cmailOldMove &&
14195             commentList[cmailOldMove] != NULL &&
14196             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14197                    "Black offers a draw" : "White offers a draw")) {
14198 #ifdef NOTDEF
14199             AppendComment(cmailOldMove, "Draw declined", TRUE);
14200             DisplayComment(cmailOldMove - 1, "Draw declined");
14201 #endif /*NOTDEF*/
14202         } else {
14203             DisplayError(_("There is no pending offer on this move"), 0);
14204         }
14205     } else {
14206         /* Not used for offers from chess program */
14207     }
14208 }
14209
14210 void
14211 RematchEvent ()
14212 {
14213     /* Issue ICS rematch command */
14214     if (appData.icsActive) {
14215         SendToICS(ics_prefix);
14216         SendToICS("rematch\n");
14217     }
14218 }
14219
14220 void
14221 CallFlagEvent ()
14222 {
14223     /* Call your opponent's flag (claim a win on time) */
14224     if (appData.icsActive) {
14225         SendToICS(ics_prefix);
14226         SendToICS("flag\n");
14227     } else {
14228         switch (gameMode) {
14229           default:
14230             return;
14231           case MachinePlaysWhite:
14232             if (whiteFlag) {
14233                 if (blackFlag)
14234                   GameEnds(GameIsDrawn, "Both players ran out of time",
14235                            GE_PLAYER);
14236                 else
14237                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14238             } else {
14239                 DisplayError(_("Your opponent is not out of time"), 0);
14240             }
14241             break;
14242           case MachinePlaysBlack:
14243             if (blackFlag) {
14244                 if (whiteFlag)
14245                   GameEnds(GameIsDrawn, "Both players ran out of time",
14246                            GE_PLAYER);
14247                 else
14248                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14249             } else {
14250                 DisplayError(_("Your opponent is not out of time"), 0);
14251             }
14252             break;
14253         }
14254     }
14255 }
14256
14257 void
14258 ClockClick (int which)
14259 {       // [HGM] code moved to back-end from winboard.c
14260         if(which) { // black clock
14261           if (gameMode == EditPosition || gameMode == IcsExamining) {
14262             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14263             SetBlackToPlayEvent();
14264           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14265           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14266           } else if (shiftKey) {
14267             AdjustClock(which, -1);
14268           } else if (gameMode == IcsPlayingWhite ||
14269                      gameMode == MachinePlaysBlack) {
14270             CallFlagEvent();
14271           }
14272         } else { // white clock
14273           if (gameMode == EditPosition || gameMode == IcsExamining) {
14274             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14275             SetWhiteToPlayEvent();
14276           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14277           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14278           } else if (shiftKey) {
14279             AdjustClock(which, -1);
14280           } else if (gameMode == IcsPlayingBlack ||
14281                    gameMode == MachinePlaysWhite) {
14282             CallFlagEvent();
14283           }
14284         }
14285 }
14286
14287 void
14288 DrawEvent ()
14289 {
14290     /* Offer draw or accept pending draw offer from opponent */
14291
14292     if (appData.icsActive) {
14293         /* Note: tournament rules require draw offers to be
14294            made after you make your move but before you punch
14295            your clock.  Currently ICS doesn't let you do that;
14296            instead, you immediately punch your clock after making
14297            a move, but you can offer a draw at any time. */
14298
14299         SendToICS(ics_prefix);
14300         SendToICS("draw\n");
14301         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14302     } else if (cmailMsgLoaded) {
14303         if (currentMove == cmailOldMove &&
14304             commentList[cmailOldMove] != NULL &&
14305             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14306                    "Black offers a draw" : "White offers a draw")) {
14307             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14308             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14309         } else if (currentMove == cmailOldMove + 1) {
14310             char *offer = WhiteOnMove(cmailOldMove) ?
14311               "White offers a draw" : "Black offers a draw";
14312             AppendComment(currentMove, offer, TRUE);
14313             DisplayComment(currentMove - 1, offer);
14314             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14315         } else {
14316             DisplayError(_("You must make your move before offering a draw"), 0);
14317             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14318         }
14319     } else if (first.offeredDraw) {
14320         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14321     } else {
14322         if (first.sendDrawOffers) {
14323             SendToProgram("draw\n", &first);
14324             userOfferedDraw = TRUE;
14325         }
14326     }
14327 }
14328
14329 void
14330 AdjournEvent ()
14331 {
14332     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14333
14334     if (appData.icsActive) {
14335         SendToICS(ics_prefix);
14336         SendToICS("adjourn\n");
14337     } else {
14338         /* Currently GNU Chess doesn't offer or accept Adjourns */
14339     }
14340 }
14341
14342
14343 void
14344 AbortEvent ()
14345 {
14346     /* Offer Abort or accept pending Abort offer from opponent */
14347
14348     if (appData.icsActive) {
14349         SendToICS(ics_prefix);
14350         SendToICS("abort\n");
14351     } else {
14352         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14353     }
14354 }
14355
14356 void
14357 ResignEvent ()
14358 {
14359     /* Resign.  You can do this even if it's not your turn. */
14360
14361     if (appData.icsActive) {
14362         SendToICS(ics_prefix);
14363         SendToICS("resign\n");
14364     } else {
14365         switch (gameMode) {
14366           case MachinePlaysWhite:
14367             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14368             break;
14369           case MachinePlaysBlack:
14370             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14371             break;
14372           case EditGame:
14373             if (cmailMsgLoaded) {
14374                 TruncateGame();
14375                 if (WhiteOnMove(cmailOldMove)) {
14376                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14377                 } else {
14378                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14379                 }
14380                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14381             }
14382             break;
14383           default:
14384             break;
14385         }
14386     }
14387 }
14388
14389
14390 void
14391 StopObservingEvent ()
14392 {
14393     /* Stop observing current games */
14394     SendToICS(ics_prefix);
14395     SendToICS("unobserve\n");
14396 }
14397
14398 void
14399 StopExaminingEvent ()
14400 {
14401     /* Stop observing current game */
14402     SendToICS(ics_prefix);
14403     SendToICS("unexamine\n");
14404 }
14405
14406 void
14407 ForwardInner (int target)
14408 {
14409     int limit; int oldSeekGraphUp = seekGraphUp;
14410
14411     if (appData.debugMode)
14412         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14413                 target, currentMove, forwardMostMove);
14414
14415     if (gameMode == EditPosition)
14416       return;
14417
14418     seekGraphUp = FALSE;
14419     MarkTargetSquares(1);
14420
14421     if (gameMode == PlayFromGameFile && !pausing)
14422       PauseEvent();
14423
14424     if (gameMode == IcsExamining && pausing)
14425       limit = pauseExamForwardMostMove;
14426     else
14427       limit = forwardMostMove;
14428
14429     if (target > limit) target = limit;
14430
14431     if (target > 0 && moveList[target - 1][0]) {
14432         int fromX, fromY, toX, toY;
14433         toX = moveList[target - 1][2] - AAA;
14434         toY = moveList[target - 1][3] - ONE;
14435         if (moveList[target - 1][1] == '@') {
14436             if (appData.highlightLastMove) {
14437                 SetHighlights(-1, -1, toX, toY);
14438             }
14439         } else {
14440             fromX = moveList[target - 1][0] - AAA;
14441             fromY = moveList[target - 1][1] - ONE;
14442             if (target == currentMove + 1) {
14443                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14444             }
14445             if (appData.highlightLastMove) {
14446                 SetHighlights(fromX, fromY, toX, toY);
14447             }
14448         }
14449     }
14450     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14451         gameMode == Training || gameMode == PlayFromGameFile ||
14452         gameMode == AnalyzeFile) {
14453         while (currentMove < target) {
14454             SendMoveToProgram(currentMove++, &first);
14455         }
14456     } else {
14457         currentMove = target;
14458     }
14459
14460     if (gameMode == EditGame || gameMode == EndOfGame) {
14461         whiteTimeRemaining = timeRemaining[0][currentMove];
14462         blackTimeRemaining = timeRemaining[1][currentMove];
14463     }
14464     DisplayBothClocks();
14465     DisplayMove(currentMove - 1);
14466     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14467     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14468     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14469         DisplayComment(currentMove - 1, commentList[currentMove]);
14470     }
14471     ClearMap(); // [HGM] exclude: invalidate map
14472 }
14473
14474
14475 void
14476 ForwardEvent ()
14477 {
14478     if (gameMode == IcsExamining && !pausing) {
14479         SendToICS(ics_prefix);
14480         SendToICS("forward\n");
14481     } else {
14482         ForwardInner(currentMove + 1);
14483     }
14484 }
14485
14486 void
14487 ToEndEvent ()
14488 {
14489     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14490         /* to optimze, we temporarily turn off analysis mode while we feed
14491          * the remaining moves to the engine. Otherwise we get analysis output
14492          * after each move.
14493          */
14494         if (first.analysisSupport) {
14495           SendToProgram("exit\nforce\n", &first);
14496           first.analyzing = FALSE;
14497         }
14498     }
14499
14500     if (gameMode == IcsExamining && !pausing) {
14501         SendToICS(ics_prefix);
14502         SendToICS("forward 999999\n");
14503     } else {
14504         ForwardInner(forwardMostMove);
14505     }
14506
14507     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14508         /* we have fed all the moves, so reactivate analysis mode */
14509         SendToProgram("analyze\n", &first);
14510         first.analyzing = TRUE;
14511         /*first.maybeThinking = TRUE;*/
14512         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14513     }
14514 }
14515
14516 void
14517 BackwardInner (int target)
14518 {
14519     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14520
14521     if (appData.debugMode)
14522         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14523                 target, currentMove, forwardMostMove);
14524
14525     if (gameMode == EditPosition) return;
14526     seekGraphUp = FALSE;
14527     MarkTargetSquares(1);
14528     if (currentMove <= backwardMostMove) {
14529         ClearHighlights();
14530         DrawPosition(full_redraw, boards[currentMove]);
14531         return;
14532     }
14533     if (gameMode == PlayFromGameFile && !pausing)
14534       PauseEvent();
14535
14536     if (moveList[target][0]) {
14537         int fromX, fromY, toX, toY;
14538         toX = moveList[target][2] - AAA;
14539         toY = moveList[target][3] - ONE;
14540         if (moveList[target][1] == '@') {
14541             if (appData.highlightLastMove) {
14542                 SetHighlights(-1, -1, toX, toY);
14543             }
14544         } else {
14545             fromX = moveList[target][0] - AAA;
14546             fromY = moveList[target][1] - ONE;
14547             if (target == currentMove - 1) {
14548                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14549             }
14550             if (appData.highlightLastMove) {
14551                 SetHighlights(fromX, fromY, toX, toY);
14552             }
14553         }
14554     }
14555     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14556         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14557         while (currentMove > target) {
14558             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14559                 // null move cannot be undone. Reload program with move history before it.
14560                 int i;
14561                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14562                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14563                 }
14564                 SendBoard(&first, i); 
14565                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14566                 break;
14567             }
14568             SendToProgram("undo\n", &first);
14569             currentMove--;
14570         }
14571     } else {
14572         currentMove = target;
14573     }
14574
14575     if (gameMode == EditGame || gameMode == EndOfGame) {
14576         whiteTimeRemaining = timeRemaining[0][currentMove];
14577         blackTimeRemaining = timeRemaining[1][currentMove];
14578     }
14579     DisplayBothClocks();
14580     DisplayMove(currentMove - 1);
14581     DrawPosition(full_redraw, boards[currentMove]);
14582     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14583     // [HGM] PV info: routine tests if comment empty
14584     DisplayComment(currentMove - 1, commentList[currentMove]);
14585     ClearMap(); // [HGM] exclude: invalidate map
14586 }
14587
14588 void
14589 BackwardEvent ()
14590 {
14591     if (gameMode == IcsExamining && !pausing) {
14592         SendToICS(ics_prefix);
14593         SendToICS("backward\n");
14594     } else {
14595         BackwardInner(currentMove - 1);
14596     }
14597 }
14598
14599 void
14600 ToStartEvent ()
14601 {
14602     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14603         /* to optimize, we temporarily turn off analysis mode while we undo
14604          * all the moves. Otherwise we get analysis output after each undo.
14605          */
14606         if (first.analysisSupport) {
14607           SendToProgram("exit\nforce\n", &first);
14608           first.analyzing = FALSE;
14609         }
14610     }
14611
14612     if (gameMode == IcsExamining && !pausing) {
14613         SendToICS(ics_prefix);
14614         SendToICS("backward 999999\n");
14615     } else {
14616         BackwardInner(backwardMostMove);
14617     }
14618
14619     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14620         /* we have fed all the moves, so reactivate analysis mode */
14621         SendToProgram("analyze\n", &first);
14622         first.analyzing = TRUE;
14623         /*first.maybeThinking = TRUE;*/
14624         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14625     }
14626 }
14627
14628 void
14629 ToNrEvent (int to)
14630 {
14631   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14632   if (to >= forwardMostMove) to = forwardMostMove;
14633   if (to <= backwardMostMove) to = backwardMostMove;
14634   if (to < currentMove) {
14635     BackwardInner(to);
14636   } else {
14637     ForwardInner(to);
14638   }
14639 }
14640
14641 void
14642 RevertEvent (Boolean annotate)
14643 {
14644     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14645         return;
14646     }
14647     if (gameMode != IcsExamining) {
14648         DisplayError(_("You are not examining a game"), 0);
14649         return;
14650     }
14651     if (pausing) {
14652         DisplayError(_("You can't revert while pausing"), 0);
14653         return;
14654     }
14655     SendToICS(ics_prefix);
14656     SendToICS("revert\n");
14657 }
14658
14659 void
14660 RetractMoveEvent ()
14661 {
14662     switch (gameMode) {
14663       case MachinePlaysWhite:
14664       case MachinePlaysBlack:
14665         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14666             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14667             return;
14668         }
14669         if (forwardMostMove < 2) return;
14670         currentMove = forwardMostMove = forwardMostMove - 2;
14671         whiteTimeRemaining = timeRemaining[0][currentMove];
14672         blackTimeRemaining = timeRemaining[1][currentMove];
14673         DisplayBothClocks();
14674         DisplayMove(currentMove - 1);
14675         ClearHighlights();/*!! could figure this out*/
14676         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14677         SendToProgram("remove\n", &first);
14678         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14679         break;
14680
14681       case BeginningOfGame:
14682       default:
14683         break;
14684
14685       case IcsPlayingWhite:
14686       case IcsPlayingBlack:
14687         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14688             SendToICS(ics_prefix);
14689             SendToICS("takeback 2\n");
14690         } else {
14691             SendToICS(ics_prefix);
14692             SendToICS("takeback 1\n");
14693         }
14694         break;
14695     }
14696 }
14697
14698 void
14699 MoveNowEvent ()
14700 {
14701     ChessProgramState *cps;
14702
14703     switch (gameMode) {
14704       case MachinePlaysWhite:
14705         if (!WhiteOnMove(forwardMostMove)) {
14706             DisplayError(_("It is your turn"), 0);
14707             return;
14708         }
14709         cps = &first;
14710         break;
14711       case MachinePlaysBlack:
14712         if (WhiteOnMove(forwardMostMove)) {
14713             DisplayError(_("It is your turn"), 0);
14714             return;
14715         }
14716         cps = &first;
14717         break;
14718       case TwoMachinesPlay:
14719         if (WhiteOnMove(forwardMostMove) ==
14720             (first.twoMachinesColor[0] == 'w')) {
14721             cps = &first;
14722         } else {
14723             cps = &second;
14724         }
14725         break;
14726       case BeginningOfGame:
14727       default:
14728         return;
14729     }
14730     SendToProgram("?\n", cps);
14731 }
14732
14733 void
14734 TruncateGameEvent ()
14735 {
14736     EditGameEvent();
14737     if (gameMode != EditGame) return;
14738     TruncateGame();
14739 }
14740
14741 void
14742 TruncateGame ()
14743 {
14744     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14745     if (forwardMostMove > currentMove) {
14746         if (gameInfo.resultDetails != NULL) {
14747             free(gameInfo.resultDetails);
14748             gameInfo.resultDetails = NULL;
14749             gameInfo.result = GameUnfinished;
14750         }
14751         forwardMostMove = currentMove;
14752         HistorySet(parseList, backwardMostMove, forwardMostMove,
14753                    currentMove-1);
14754     }
14755 }
14756
14757 void
14758 HintEvent ()
14759 {
14760     if (appData.noChessProgram) return;
14761     switch (gameMode) {
14762       case MachinePlaysWhite:
14763         if (WhiteOnMove(forwardMostMove)) {
14764             DisplayError(_("Wait until your turn"), 0);
14765             return;
14766         }
14767         break;
14768       case BeginningOfGame:
14769       case MachinePlaysBlack:
14770         if (!WhiteOnMove(forwardMostMove)) {
14771             DisplayError(_("Wait until your turn"), 0);
14772             return;
14773         }
14774         break;
14775       default:
14776         DisplayError(_("No hint available"), 0);
14777         return;
14778     }
14779     SendToProgram("hint\n", &first);
14780     hintRequested = TRUE;
14781 }
14782
14783 void
14784 BookEvent ()
14785 {
14786     if (appData.noChessProgram) return;
14787     switch (gameMode) {
14788       case MachinePlaysWhite:
14789         if (WhiteOnMove(forwardMostMove)) {
14790             DisplayError(_("Wait until your turn"), 0);
14791             return;
14792         }
14793         break;
14794       case BeginningOfGame:
14795       case MachinePlaysBlack:
14796         if (!WhiteOnMove(forwardMostMove)) {
14797             DisplayError(_("Wait until your turn"), 0);
14798             return;
14799         }
14800         break;
14801       case EditPosition:
14802         EditPositionDone(TRUE);
14803         break;
14804       case TwoMachinesPlay:
14805         return;
14806       default:
14807         break;
14808     }
14809     SendToProgram("bk\n", &first);
14810     bookOutput[0] = NULLCHAR;
14811     bookRequested = TRUE;
14812 }
14813
14814 void
14815 AboutGameEvent ()
14816 {
14817     char *tags = PGNTags(&gameInfo);
14818     TagsPopUp(tags, CmailMsg());
14819     free(tags);
14820 }
14821
14822 /* end button procedures */
14823
14824 void
14825 PrintPosition (FILE *fp, int move)
14826 {
14827     int i, j;
14828
14829     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14830         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14831             char c = PieceToChar(boards[move][i][j]);
14832             fputc(c == 'x' ? '.' : c, fp);
14833             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14834         }
14835     }
14836     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14837       fprintf(fp, "white to play\n");
14838     else
14839       fprintf(fp, "black to play\n");
14840 }
14841
14842 void
14843 PrintOpponents (FILE *fp)
14844 {
14845     if (gameInfo.white != NULL) {
14846         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14847     } else {
14848         fprintf(fp, "\n");
14849     }
14850 }
14851
14852 /* Find last component of program's own name, using some heuristics */
14853 void
14854 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14855 {
14856     char *p, *q, c;
14857     int local = (strcmp(host, "localhost") == 0);
14858     while (!local && (p = strchr(prog, ';')) != NULL) {
14859         p++;
14860         while (*p == ' ') p++;
14861         prog = p;
14862     }
14863     if (*prog == '"' || *prog == '\'') {
14864         q = strchr(prog + 1, *prog);
14865     } else {
14866         q = strchr(prog, ' ');
14867     }
14868     if (q == NULL) q = prog + strlen(prog);
14869     p = q;
14870     while (p >= prog && *p != '/' && *p != '\\') p--;
14871     p++;
14872     if(p == prog && *p == '"') p++;
14873     c = *q; *q = 0;
14874     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14875     memcpy(buf, p, q - p);
14876     buf[q - p] = NULLCHAR;
14877     if (!local) {
14878         strcat(buf, "@");
14879         strcat(buf, host);
14880     }
14881 }
14882
14883 char *
14884 TimeControlTagValue ()
14885 {
14886     char buf[MSG_SIZ];
14887     if (!appData.clockMode) {
14888       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14889     } else if (movesPerSession > 0) {
14890       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14891     } else if (timeIncrement == 0) {
14892       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14893     } else {
14894       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14895     }
14896     return StrSave(buf);
14897 }
14898
14899 void
14900 SetGameInfo ()
14901 {
14902     /* This routine is used only for certain modes */
14903     VariantClass v = gameInfo.variant;
14904     ChessMove r = GameUnfinished;
14905     char *p = NULL;
14906
14907     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14908         r = gameInfo.result;
14909         p = gameInfo.resultDetails;
14910         gameInfo.resultDetails = NULL;
14911     }
14912     ClearGameInfo(&gameInfo);
14913     gameInfo.variant = v;
14914
14915     switch (gameMode) {
14916       case MachinePlaysWhite:
14917         gameInfo.event = StrSave( appData.pgnEventHeader );
14918         gameInfo.site = StrSave(HostName());
14919         gameInfo.date = PGNDate();
14920         gameInfo.round = StrSave("-");
14921         gameInfo.white = StrSave(first.tidy);
14922         gameInfo.black = StrSave(UserName());
14923         gameInfo.timeControl = TimeControlTagValue();
14924         break;
14925
14926       case MachinePlaysBlack:
14927         gameInfo.event = StrSave( appData.pgnEventHeader );
14928         gameInfo.site = StrSave(HostName());
14929         gameInfo.date = PGNDate();
14930         gameInfo.round = StrSave("-");
14931         gameInfo.white = StrSave(UserName());
14932         gameInfo.black = StrSave(first.tidy);
14933         gameInfo.timeControl = TimeControlTagValue();
14934         break;
14935
14936       case TwoMachinesPlay:
14937         gameInfo.event = StrSave( appData.pgnEventHeader );
14938         gameInfo.site = StrSave(HostName());
14939         gameInfo.date = PGNDate();
14940         if (roundNr > 0) {
14941             char buf[MSG_SIZ];
14942             snprintf(buf, MSG_SIZ, "%d", roundNr);
14943             gameInfo.round = StrSave(buf);
14944         } else {
14945             gameInfo.round = StrSave("-");
14946         }
14947         if (first.twoMachinesColor[0] == 'w') {
14948             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14949             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14950         } else {
14951             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14952             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14953         }
14954         gameInfo.timeControl = TimeControlTagValue();
14955         break;
14956
14957       case EditGame:
14958         gameInfo.event = StrSave("Edited game");
14959         gameInfo.site = StrSave(HostName());
14960         gameInfo.date = PGNDate();
14961         gameInfo.round = StrSave("-");
14962         gameInfo.white = StrSave("-");
14963         gameInfo.black = StrSave("-");
14964         gameInfo.result = r;
14965         gameInfo.resultDetails = p;
14966         break;
14967
14968       case EditPosition:
14969         gameInfo.event = StrSave("Edited position");
14970         gameInfo.site = StrSave(HostName());
14971         gameInfo.date = PGNDate();
14972         gameInfo.round = StrSave("-");
14973         gameInfo.white = StrSave("-");
14974         gameInfo.black = StrSave("-");
14975         break;
14976
14977       case IcsPlayingWhite:
14978       case IcsPlayingBlack:
14979       case IcsObserving:
14980       case IcsExamining:
14981         break;
14982
14983       case PlayFromGameFile:
14984         gameInfo.event = StrSave("Game from non-PGN file");
14985         gameInfo.site = StrSave(HostName());
14986         gameInfo.date = PGNDate();
14987         gameInfo.round = StrSave("-");
14988         gameInfo.white = StrSave("?");
14989         gameInfo.black = StrSave("?");
14990         break;
14991
14992       default:
14993         break;
14994     }
14995 }
14996
14997 void
14998 ReplaceComment (int index, char *text)
14999 {
15000     int len;
15001     char *p;
15002     float score;
15003
15004     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15005        pvInfoList[index-1].depth == len &&
15006        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15007        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15008     while (*text == '\n') text++;
15009     len = strlen(text);
15010     while (len > 0 && text[len - 1] == '\n') len--;
15011
15012     if (commentList[index] != NULL)
15013       free(commentList[index]);
15014
15015     if (len == 0) {
15016         commentList[index] = NULL;
15017         return;
15018     }
15019   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15020       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15021       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15022     commentList[index] = (char *) malloc(len + 2);
15023     strncpy(commentList[index], text, len);
15024     commentList[index][len] = '\n';
15025     commentList[index][len + 1] = NULLCHAR;
15026   } else {
15027     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15028     char *p;
15029     commentList[index] = (char *) malloc(len + 7);
15030     safeStrCpy(commentList[index], "{\n", 3);
15031     safeStrCpy(commentList[index]+2, text, len+1);
15032     commentList[index][len+2] = NULLCHAR;
15033     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15034     strcat(commentList[index], "\n}\n");
15035   }
15036 }
15037
15038 void
15039 CrushCRs (char *text)
15040 {
15041   char *p = text;
15042   char *q = text;
15043   char ch;
15044
15045   do {
15046     ch = *p++;
15047     if (ch == '\r') continue;
15048     *q++ = ch;
15049   } while (ch != '\0');
15050 }
15051
15052 void
15053 AppendComment (int index, char *text, Boolean addBraces)
15054 /* addBraces  tells if we should add {} */
15055 {
15056     int oldlen, len;
15057     char *old;
15058
15059 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15060     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15061
15062     CrushCRs(text);
15063     while (*text == '\n') text++;
15064     len = strlen(text);
15065     while (len > 0 && text[len - 1] == '\n') len--;
15066     text[len] = NULLCHAR;
15067
15068     if (len == 0) return;
15069
15070     if (commentList[index] != NULL) {
15071       Boolean addClosingBrace = addBraces;
15072         old = commentList[index];
15073         oldlen = strlen(old);
15074         while(commentList[index][oldlen-1] ==  '\n')
15075           commentList[index][--oldlen] = NULLCHAR;
15076         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15077         safeStrCpy(commentList[index], old, oldlen + len + 6);
15078         free(old);
15079         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15080         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15081           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15082           while (*text == '\n') { text++; len--; }
15083           commentList[index][--oldlen] = NULLCHAR;
15084       }
15085         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15086         else          strcat(commentList[index], "\n");
15087         strcat(commentList[index], text);
15088         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15089         else          strcat(commentList[index], "\n");
15090     } else {
15091         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15092         if(addBraces)
15093           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15094         else commentList[index][0] = NULLCHAR;
15095         strcat(commentList[index], text);
15096         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15097         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15098     }
15099 }
15100
15101 static char *
15102 FindStr (char * text, char * sub_text)
15103 {
15104     char * result = strstr( text, sub_text );
15105
15106     if( result != NULL ) {
15107         result += strlen( sub_text );
15108     }
15109
15110     return result;
15111 }
15112
15113 /* [AS] Try to extract PV info from PGN comment */
15114 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15115 char *
15116 GetInfoFromComment (int index, char * text)
15117 {
15118     char * sep = text, *p;
15119
15120     if( text != NULL && index > 0 ) {
15121         int score = 0;
15122         int depth = 0;
15123         int time = -1, sec = 0, deci;
15124         char * s_eval = FindStr( text, "[%eval " );
15125         char * s_emt = FindStr( text, "[%emt " );
15126
15127         if( s_eval != NULL || s_emt != NULL ) {
15128             /* New style */
15129             char delim;
15130
15131             if( s_eval != NULL ) {
15132                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15133                     return text;
15134                 }
15135
15136                 if( delim != ']' ) {
15137                     return text;
15138                 }
15139             }
15140
15141             if( s_emt != NULL ) {
15142             }
15143                 return text;
15144         }
15145         else {
15146             /* We expect something like: [+|-]nnn.nn/dd */
15147             int score_lo = 0;
15148
15149             if(*text != '{') return text; // [HGM] braces: must be normal comment
15150
15151             sep = strchr( text, '/' );
15152             if( sep == NULL || sep < (text+4) ) {
15153                 return text;
15154             }
15155
15156             p = text;
15157             if(p[1] == '(') { // comment starts with PV
15158                p = strchr(p, ')'); // locate end of PV
15159                if(p == NULL || sep < p+5) return text;
15160                // at this point we have something like "{(.*) +0.23/6 ..."
15161                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15162                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15163                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15164             }
15165             time = -1; sec = -1; deci = -1;
15166             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15167                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15168                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15169                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15170                 return text;
15171             }
15172
15173             if( score_lo < 0 || score_lo >= 100 ) {
15174                 return text;
15175             }
15176
15177             if(sec >= 0) time = 600*time + 10*sec; else
15178             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15179
15180             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15181
15182             /* [HGM] PV time: now locate end of PV info */
15183             while( *++sep >= '0' && *sep <= '9'); // strip depth
15184             if(time >= 0)
15185             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15186             if(sec >= 0)
15187             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15188             if(deci >= 0)
15189             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15190             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15191         }
15192
15193         if( depth <= 0 ) {
15194             return text;
15195         }
15196
15197         if( time < 0 ) {
15198             time = -1;
15199         }
15200
15201         pvInfoList[index-1].depth = depth;
15202         pvInfoList[index-1].score = score;
15203         pvInfoList[index-1].time  = 10*time; // centi-sec
15204         if(*sep == '}') *sep = 0; else *--sep = '{';
15205         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15206     }
15207     return sep;
15208 }
15209
15210 void
15211 SendToProgram (char *message, ChessProgramState *cps)
15212 {
15213     int count, outCount, error;
15214     char buf[MSG_SIZ];
15215
15216     if (cps->pr == NoProc) return;
15217     Attention(cps);
15218
15219     if (appData.debugMode) {
15220         TimeMark now;
15221         GetTimeMark(&now);
15222         fprintf(debugFP, "%ld >%-6s: %s",
15223                 SubtractTimeMarks(&now, &programStartTime),
15224                 cps->which, message);
15225         if(serverFP)
15226             fprintf(serverFP, "%ld >%-6s: %s",
15227                 SubtractTimeMarks(&now, &programStartTime),
15228                 cps->which, message), fflush(serverFP);
15229     }
15230
15231     count = strlen(message);
15232     outCount = OutputToProcess(cps->pr, message, count, &error);
15233     if (outCount < count && !exiting
15234                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15235       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15236       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15237         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15238             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15239                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15240                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15241                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15242             } else {
15243                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15244                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15245                 gameInfo.result = res;
15246             }
15247             gameInfo.resultDetails = StrSave(buf);
15248         }
15249         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15250         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15251     }
15252 }
15253
15254 void
15255 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15256 {
15257     char *end_str;
15258     char buf[MSG_SIZ];
15259     ChessProgramState *cps = (ChessProgramState *)closure;
15260
15261     if (isr != cps->isr) return; /* Killed intentionally */
15262     if (count <= 0) {
15263         if (count == 0) {
15264             RemoveInputSource(cps->isr);
15265             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15266             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15267                     _(cps->which), cps->program);
15268         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15269                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15270                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15271                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15272                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15273                 } else {
15274                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15275                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15276                     gameInfo.result = res;
15277                 }
15278                 gameInfo.resultDetails = StrSave(buf);
15279             }
15280             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15281             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15282         } else {
15283             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15284                     _(cps->which), cps->program);
15285             RemoveInputSource(cps->isr);
15286
15287             /* [AS] Program is misbehaving badly... kill it */
15288             if( count == -2 ) {
15289                 DestroyChildProcess( cps->pr, 9 );
15290                 cps->pr = NoProc;
15291             }
15292
15293             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15294         }
15295         return;
15296     }
15297
15298     if ((end_str = strchr(message, '\r')) != NULL)
15299       *end_str = NULLCHAR;
15300     if ((end_str = strchr(message, '\n')) != NULL)
15301       *end_str = NULLCHAR;
15302
15303     if (appData.debugMode) {
15304         TimeMark now; int print = 1;
15305         char *quote = ""; char c; int i;
15306
15307         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15308                 char start = message[0];
15309                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15310                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15311                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15312                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15313                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15314                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15315                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15316                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15317                    sscanf(message, "hint: %c", &c)!=1 && 
15318                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15319                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15320                     print = (appData.engineComments >= 2);
15321                 }
15322                 message[0] = start; // restore original message
15323         }
15324         if(print) {
15325                 GetTimeMark(&now);
15326                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15327                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15328                         quote,
15329                         message);
15330                 if(serverFP)
15331                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15332                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15333                         quote,
15334                         message), fflush(serverFP);
15335         }
15336     }
15337
15338     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15339     if (appData.icsEngineAnalyze) {
15340         if (strstr(message, "whisper") != NULL ||
15341              strstr(message, "kibitz") != NULL ||
15342             strstr(message, "tellics") != NULL) return;
15343     }
15344
15345     HandleMachineMove(message, cps);
15346 }
15347
15348
15349 void
15350 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15351 {
15352     char buf[MSG_SIZ];
15353     int seconds;
15354
15355     if( timeControl_2 > 0 ) {
15356         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15357             tc = timeControl_2;
15358         }
15359     }
15360     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15361     inc /= cps->timeOdds;
15362     st  /= cps->timeOdds;
15363
15364     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15365
15366     if (st > 0) {
15367       /* Set exact time per move, normally using st command */
15368       if (cps->stKludge) {
15369         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15370         seconds = st % 60;
15371         if (seconds == 0) {
15372           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15373         } else {
15374           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15375         }
15376       } else {
15377         snprintf(buf, MSG_SIZ, "st %d\n", st);
15378       }
15379     } else {
15380       /* Set conventional or incremental time control, using level command */
15381       if (seconds == 0) {
15382         /* Note old gnuchess bug -- minutes:seconds used to not work.
15383            Fixed in later versions, but still avoid :seconds
15384            when seconds is 0. */
15385         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15386       } else {
15387         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15388                  seconds, inc/1000.);
15389       }
15390     }
15391     SendToProgram(buf, cps);
15392
15393     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15394     /* Orthogonally, limit search to given depth */
15395     if (sd > 0) {
15396       if (cps->sdKludge) {
15397         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15398       } else {
15399         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15400       }
15401       SendToProgram(buf, cps);
15402     }
15403
15404     if(cps->nps >= 0) { /* [HGM] nps */
15405         if(cps->supportsNPS == FALSE)
15406           cps->nps = -1; // don't use if engine explicitly says not supported!
15407         else {
15408           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15409           SendToProgram(buf, cps);
15410         }
15411     }
15412 }
15413
15414 ChessProgramState *
15415 WhitePlayer ()
15416 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15417 {
15418     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15419        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15420         return &second;
15421     return &first;
15422 }
15423
15424 void
15425 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15426 {
15427     char message[MSG_SIZ];
15428     long time, otime;
15429
15430     /* Note: this routine must be called when the clocks are stopped
15431        or when they have *just* been set or switched; otherwise
15432        it will be off by the time since the current tick started.
15433     */
15434     if (machineWhite) {
15435         time = whiteTimeRemaining / 10;
15436         otime = blackTimeRemaining / 10;
15437     } else {
15438         time = blackTimeRemaining / 10;
15439         otime = whiteTimeRemaining / 10;
15440     }
15441     /* [HGM] translate opponent's time by time-odds factor */
15442     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15443
15444     if (time <= 0) time = 1;
15445     if (otime <= 0) otime = 1;
15446
15447     snprintf(message, MSG_SIZ, "time %ld\n", time);
15448     SendToProgram(message, cps);
15449
15450     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15451     SendToProgram(message, cps);
15452 }
15453
15454 int
15455 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15456 {
15457   char buf[MSG_SIZ];
15458   int len = strlen(name);
15459   int val;
15460
15461   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15462     (*p) += len + 1;
15463     sscanf(*p, "%d", &val);
15464     *loc = (val != 0);
15465     while (**p && **p != ' ')
15466       (*p)++;
15467     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15468     SendToProgram(buf, cps);
15469     return TRUE;
15470   }
15471   return FALSE;
15472 }
15473
15474 int
15475 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15476 {
15477   char buf[MSG_SIZ];
15478   int len = strlen(name);
15479   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15480     (*p) += len + 1;
15481     sscanf(*p, "%d", loc);
15482     while (**p && **p != ' ') (*p)++;
15483     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15484     SendToProgram(buf, cps);
15485     return TRUE;
15486   }
15487   return FALSE;
15488 }
15489
15490 int
15491 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15492 {
15493   char buf[MSG_SIZ];
15494   int len = strlen(name);
15495   if (strncmp((*p), name, len) == 0
15496       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15497     (*p) += len + 2;
15498     sscanf(*p, "%[^\"]", loc);
15499     while (**p && **p != '\"') (*p)++;
15500     if (**p == '\"') (*p)++;
15501     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15502     SendToProgram(buf, cps);
15503     return TRUE;
15504   }
15505   return FALSE;
15506 }
15507
15508 int
15509 ParseOption (Option *opt, ChessProgramState *cps)
15510 // [HGM] options: process the string that defines an engine option, and determine
15511 // name, type, default value, and allowed value range
15512 {
15513         char *p, *q, buf[MSG_SIZ];
15514         int n, min = (-1)<<31, max = 1<<31, def;
15515
15516         if(p = strstr(opt->name, " -spin ")) {
15517             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15518             if(max < min) max = min; // enforce consistency
15519             if(def < min) def = min;
15520             if(def > max) def = max;
15521             opt->value = def;
15522             opt->min = min;
15523             opt->max = max;
15524             opt->type = Spin;
15525         } else if((p = strstr(opt->name, " -slider "))) {
15526             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15527             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15528             if(max < min) max = min; // enforce consistency
15529             if(def < min) def = min;
15530             if(def > max) def = max;
15531             opt->value = def;
15532             opt->min = min;
15533             opt->max = max;
15534             opt->type = Spin; // Slider;
15535         } else if((p = strstr(opt->name, " -string "))) {
15536             opt->textValue = p+9;
15537             opt->type = TextBox;
15538         } else if((p = strstr(opt->name, " -file "))) {
15539             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15540             opt->textValue = p+7;
15541             opt->type = FileName; // FileName;
15542         } else if((p = strstr(opt->name, " -path "))) {
15543             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15544             opt->textValue = p+7;
15545             opt->type = PathName; // PathName;
15546         } else if(p = strstr(opt->name, " -check ")) {
15547             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15548             opt->value = (def != 0);
15549             opt->type = CheckBox;
15550         } else if(p = strstr(opt->name, " -combo ")) {
15551             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15552             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15553             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15554             opt->value = n = 0;
15555             while(q = StrStr(q, " /// ")) {
15556                 n++; *q = 0;    // count choices, and null-terminate each of them
15557                 q += 5;
15558                 if(*q == '*') { // remember default, which is marked with * prefix
15559                     q++;
15560                     opt->value = n;
15561                 }
15562                 cps->comboList[cps->comboCnt++] = q;
15563             }
15564             cps->comboList[cps->comboCnt++] = NULL;
15565             opt->max = n + 1;
15566             opt->type = ComboBox;
15567         } else if(p = strstr(opt->name, " -button")) {
15568             opt->type = Button;
15569         } else if(p = strstr(opt->name, " -save")) {
15570             opt->type = SaveButton;
15571         } else return FALSE;
15572         *p = 0; // terminate option name
15573         // now look if the command-line options define a setting for this engine option.
15574         if(cps->optionSettings && cps->optionSettings[0])
15575             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15576         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15577           snprintf(buf, MSG_SIZ, "option %s", p);
15578                 if(p = strstr(buf, ",")) *p = 0;
15579                 if(q = strchr(buf, '=')) switch(opt->type) {
15580                     case ComboBox:
15581                         for(n=0; n<opt->max; n++)
15582                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15583                         break;
15584                     case TextBox:
15585                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15586                         break;
15587                     case Spin:
15588                     case CheckBox:
15589                         opt->value = atoi(q+1);
15590                     default:
15591                         break;
15592                 }
15593                 strcat(buf, "\n");
15594                 SendToProgram(buf, cps);
15595         }
15596         return TRUE;
15597 }
15598
15599 void
15600 FeatureDone (ChessProgramState *cps, int val)
15601 {
15602   DelayedEventCallback cb = GetDelayedEvent();
15603   if ((cb == InitBackEnd3 && cps == &first) ||
15604       (cb == SettingsMenuIfReady && cps == &second) ||
15605       (cb == LoadEngine) ||
15606       (cb == TwoMachinesEventIfReady)) {
15607     CancelDelayedEvent();
15608     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15609   }
15610   cps->initDone = val;
15611 }
15612
15613 /* Parse feature command from engine */
15614 void
15615 ParseFeatures (char *args, ChessProgramState *cps)
15616 {
15617   char *p = args;
15618   char *q;
15619   int val;
15620   char buf[MSG_SIZ];
15621
15622   for (;;) {
15623     while (*p == ' ') p++;
15624     if (*p == NULLCHAR) return;
15625
15626     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15627     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15628     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15629     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15630     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15631     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15632     if (BoolFeature(&p, "reuse", &val, cps)) {
15633       /* Engine can disable reuse, but can't enable it if user said no */
15634       if (!val) cps->reuse = FALSE;
15635       continue;
15636     }
15637     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15638     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15639       if (gameMode == TwoMachinesPlay) {
15640         DisplayTwoMachinesTitle();
15641       } else {
15642         DisplayTitle("");
15643       }
15644       continue;
15645     }
15646     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15647     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15648     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15649     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15650     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15651     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15652     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15653     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15654     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15655     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15656     if (IntFeature(&p, "done", &val, cps)) {
15657       FeatureDone(cps, val);
15658       continue;
15659     }
15660     /* Added by Tord: */
15661     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15662     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15663     /* End of additions by Tord */
15664
15665     /* [HGM] added features: */
15666     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15667     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15668     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15669     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15670     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15671     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15672     if (StringFeature(&p, "option", buf, cps)) {
15673         FREE(cps->option[cps->nrOptions].name);
15674         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15675         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15676         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15677           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15678             SendToProgram(buf, cps);
15679             continue;
15680         }
15681         if(cps->nrOptions >= MAX_OPTIONS) {
15682             cps->nrOptions--;
15683             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15684             DisplayError(buf, 0);
15685         }
15686         continue;
15687     }
15688     /* End of additions by HGM */
15689
15690     /* unknown feature: complain and skip */
15691     q = p;
15692     while (*q && *q != '=') q++;
15693     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15694     SendToProgram(buf, cps);
15695     p = q;
15696     if (*p == '=') {
15697       p++;
15698       if (*p == '\"') {
15699         p++;
15700         while (*p && *p != '\"') p++;
15701         if (*p == '\"') p++;
15702       } else {
15703         while (*p && *p != ' ') p++;
15704       }
15705     }
15706   }
15707
15708 }
15709
15710 void
15711 PeriodicUpdatesEvent (int newState)
15712 {
15713     if (newState == appData.periodicUpdates)
15714       return;
15715
15716     appData.periodicUpdates=newState;
15717
15718     /* Display type changes, so update it now */
15719 //    DisplayAnalysis();
15720
15721     /* Get the ball rolling again... */
15722     if (newState) {
15723         AnalysisPeriodicEvent(1);
15724         StartAnalysisClock();
15725     }
15726 }
15727
15728 void
15729 PonderNextMoveEvent (int newState)
15730 {
15731     if (newState == appData.ponderNextMove) return;
15732     if (gameMode == EditPosition) EditPositionDone(TRUE);
15733     if (newState) {
15734         SendToProgram("hard\n", &first);
15735         if (gameMode == TwoMachinesPlay) {
15736             SendToProgram("hard\n", &second);
15737         }
15738     } else {
15739         SendToProgram("easy\n", &first);
15740         thinkOutput[0] = NULLCHAR;
15741         if (gameMode == TwoMachinesPlay) {
15742             SendToProgram("easy\n", &second);
15743         }
15744     }
15745     appData.ponderNextMove = newState;
15746 }
15747
15748 void
15749 NewSettingEvent (int option, int *feature, char *command, int value)
15750 {
15751     char buf[MSG_SIZ];
15752
15753     if (gameMode == EditPosition) EditPositionDone(TRUE);
15754     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15755     if(feature == NULL || *feature) SendToProgram(buf, &first);
15756     if (gameMode == TwoMachinesPlay) {
15757         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15758     }
15759 }
15760
15761 void
15762 ShowThinkingEvent ()
15763 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15764 {
15765     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15766     int newState = appData.showThinking
15767         // [HGM] thinking: other features now need thinking output as well
15768         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15769
15770     if (oldState == newState) return;
15771     oldState = newState;
15772     if (gameMode == EditPosition) EditPositionDone(TRUE);
15773     if (oldState) {
15774         SendToProgram("post\n", &first);
15775         if (gameMode == TwoMachinesPlay) {
15776             SendToProgram("post\n", &second);
15777         }
15778     } else {
15779         SendToProgram("nopost\n", &first);
15780         thinkOutput[0] = NULLCHAR;
15781         if (gameMode == TwoMachinesPlay) {
15782             SendToProgram("nopost\n", &second);
15783         }
15784     }
15785 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15786 }
15787
15788 void
15789 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15790 {
15791   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15792   if (pr == NoProc) return;
15793   AskQuestion(title, question, replyPrefix, pr);
15794 }
15795
15796 void
15797 TypeInEvent (char firstChar)
15798 {
15799     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15800         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15801         gameMode == AnalyzeMode || gameMode == EditGame || 
15802         gameMode == EditPosition || gameMode == IcsExamining ||
15803         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15804         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15805                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15806                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15807         gameMode == Training) PopUpMoveDialog(firstChar);
15808 }
15809
15810 void
15811 TypeInDoneEvent (char *move)
15812 {
15813         Board board;
15814         int n, fromX, fromY, toX, toY;
15815         char promoChar;
15816         ChessMove moveType;
15817
15818         // [HGM] FENedit
15819         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15820                 EditPositionPasteFEN(move);
15821                 return;
15822         }
15823         // [HGM] movenum: allow move number to be typed in any mode
15824         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15825           ToNrEvent(2*n-1);
15826           return;
15827         }
15828         // undocumented kludge: allow command-line option to be typed in!
15829         // (potentially fatal, and does not implement the effect of the option.)
15830         // should only be used for options that are values on which future decisions will be made,
15831         // and definitely not on options that would be used during initialization.
15832         if(strstr(move, "!!! -") == move) {
15833             ParseArgsFromString(move+4);
15834             return;
15835         }
15836
15837       if (gameMode != EditGame && currentMove != forwardMostMove && 
15838         gameMode != Training) {
15839         DisplayMoveError(_("Displayed move is not current"));
15840       } else {
15841         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15842           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15843         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15844         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15845           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15846           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15847         } else {
15848           DisplayMoveError(_("Could not parse move"));
15849         }
15850       }
15851 }
15852
15853 void
15854 DisplayMove (int moveNumber)
15855 {
15856     char message[MSG_SIZ];
15857     char res[MSG_SIZ];
15858     char cpThinkOutput[MSG_SIZ];
15859
15860     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15861
15862     if (moveNumber == forwardMostMove - 1 ||
15863         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15864
15865         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15866
15867         if (strchr(cpThinkOutput, '\n')) {
15868             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15869         }
15870     } else {
15871         *cpThinkOutput = NULLCHAR;
15872     }
15873
15874     /* [AS] Hide thinking from human user */
15875     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15876         *cpThinkOutput = NULLCHAR;
15877         if( thinkOutput[0] != NULLCHAR ) {
15878             int i;
15879
15880             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15881                 cpThinkOutput[i] = '.';
15882             }
15883             cpThinkOutput[i] = NULLCHAR;
15884             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15885         }
15886     }
15887
15888     if (moveNumber == forwardMostMove - 1 &&
15889         gameInfo.resultDetails != NULL) {
15890         if (gameInfo.resultDetails[0] == NULLCHAR) {
15891           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15892         } else {
15893           snprintf(res, MSG_SIZ, " {%s} %s",
15894                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15895         }
15896     } else {
15897         res[0] = NULLCHAR;
15898     }
15899
15900     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15901         DisplayMessage(res, cpThinkOutput);
15902     } else {
15903       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15904                 WhiteOnMove(moveNumber) ? " " : ".. ",
15905                 parseList[moveNumber], res);
15906         DisplayMessage(message, cpThinkOutput);
15907     }
15908 }
15909
15910 void
15911 DisplayComment (int moveNumber, char *text)
15912 {
15913     char title[MSG_SIZ];
15914
15915     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15916       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15917     } else {
15918       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15919               WhiteOnMove(moveNumber) ? " " : ".. ",
15920               parseList[moveNumber]);
15921     }
15922     if (text != NULL && (appData.autoDisplayComment || commentUp))
15923         CommentPopUp(title, text);
15924 }
15925
15926 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15927  * might be busy thinking or pondering.  It can be omitted if your
15928  * gnuchess is configured to stop thinking immediately on any user
15929  * input.  However, that gnuchess feature depends on the FIONREAD
15930  * ioctl, which does not work properly on some flavors of Unix.
15931  */
15932 void
15933 Attention (ChessProgramState *cps)
15934 {
15935 #if ATTENTION
15936     if (!cps->useSigint) return;
15937     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15938     switch (gameMode) {
15939       case MachinePlaysWhite:
15940       case MachinePlaysBlack:
15941       case TwoMachinesPlay:
15942       case IcsPlayingWhite:
15943       case IcsPlayingBlack:
15944       case AnalyzeMode:
15945       case AnalyzeFile:
15946         /* Skip if we know it isn't thinking */
15947         if (!cps->maybeThinking) return;
15948         if (appData.debugMode)
15949           fprintf(debugFP, "Interrupting %s\n", cps->which);
15950         InterruptChildProcess(cps->pr);
15951         cps->maybeThinking = FALSE;
15952         break;
15953       default:
15954         break;
15955     }
15956 #endif /*ATTENTION*/
15957 }
15958
15959 int
15960 CheckFlags ()
15961 {
15962     if (whiteTimeRemaining <= 0) {
15963         if (!whiteFlag) {
15964             whiteFlag = TRUE;
15965             if (appData.icsActive) {
15966                 if (appData.autoCallFlag &&
15967                     gameMode == IcsPlayingBlack && !blackFlag) {
15968                   SendToICS(ics_prefix);
15969                   SendToICS("flag\n");
15970                 }
15971             } else {
15972                 if (blackFlag) {
15973                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15974                 } else {
15975                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15976                     if (appData.autoCallFlag) {
15977                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15978                         return TRUE;
15979                     }
15980                 }
15981             }
15982         }
15983     }
15984     if (blackTimeRemaining <= 0) {
15985         if (!blackFlag) {
15986             blackFlag = TRUE;
15987             if (appData.icsActive) {
15988                 if (appData.autoCallFlag &&
15989                     gameMode == IcsPlayingWhite && !whiteFlag) {
15990                   SendToICS(ics_prefix);
15991                   SendToICS("flag\n");
15992                 }
15993             } else {
15994                 if (whiteFlag) {
15995                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15996                 } else {
15997                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15998                     if (appData.autoCallFlag) {
15999                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16000                         return TRUE;
16001                     }
16002                 }
16003             }
16004         }
16005     }
16006     return FALSE;
16007 }
16008
16009 void
16010 CheckTimeControl ()
16011 {
16012     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16013         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16014
16015     /*
16016      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16017      */
16018     if ( !WhiteOnMove(forwardMostMove) ) {
16019         /* White made time control */
16020         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16021         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16022         /* [HGM] time odds: correct new time quota for time odds! */
16023                                             / WhitePlayer()->timeOdds;
16024         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16025     } else {
16026         lastBlack -= blackTimeRemaining;
16027         /* Black made time control */
16028         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16029                                             / WhitePlayer()->other->timeOdds;
16030         lastWhite = whiteTimeRemaining;
16031     }
16032 }
16033
16034 void
16035 DisplayBothClocks ()
16036 {
16037     int wom = gameMode == EditPosition ?
16038       !blackPlaysFirst : WhiteOnMove(currentMove);
16039     DisplayWhiteClock(whiteTimeRemaining, wom);
16040     DisplayBlackClock(blackTimeRemaining, !wom);
16041 }
16042
16043
16044 /* Timekeeping seems to be a portability nightmare.  I think everyone
16045    has ftime(), but I'm really not sure, so I'm including some ifdefs
16046    to use other calls if you don't.  Clocks will be less accurate if
16047    you have neither ftime nor gettimeofday.
16048 */
16049
16050 /* VS 2008 requires the #include outside of the function */
16051 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16052 #include <sys/timeb.h>
16053 #endif
16054
16055 /* Get the current time as a TimeMark */
16056 void
16057 GetTimeMark (TimeMark *tm)
16058 {
16059 #if HAVE_GETTIMEOFDAY
16060
16061     struct timeval timeVal;
16062     struct timezone timeZone;
16063
16064     gettimeofday(&timeVal, &timeZone);
16065     tm->sec = (long) timeVal.tv_sec;
16066     tm->ms = (int) (timeVal.tv_usec / 1000L);
16067
16068 #else /*!HAVE_GETTIMEOFDAY*/
16069 #if HAVE_FTIME
16070
16071 // include <sys/timeb.h> / moved to just above start of function
16072     struct timeb timeB;
16073
16074     ftime(&timeB);
16075     tm->sec = (long) timeB.time;
16076     tm->ms = (int) timeB.millitm;
16077
16078 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16079     tm->sec = (long) time(NULL);
16080     tm->ms = 0;
16081 #endif
16082 #endif
16083 }
16084
16085 /* Return the difference in milliseconds between two
16086    time marks.  We assume the difference will fit in a long!
16087 */
16088 long
16089 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16090 {
16091     return 1000L*(tm2->sec - tm1->sec) +
16092            (long) (tm2->ms - tm1->ms);
16093 }
16094
16095
16096 /*
16097  * Code to manage the game clocks.
16098  *
16099  * In tournament play, black starts the clock and then white makes a move.
16100  * We give the human user a slight advantage if he is playing white---the
16101  * clocks don't run until he makes his first move, so it takes zero time.
16102  * Also, we don't account for network lag, so we could get out of sync
16103  * with GNU Chess's clock -- but then, referees are always right.
16104  */
16105
16106 static TimeMark tickStartTM;
16107 static long intendedTickLength;
16108
16109 long
16110 NextTickLength (long timeRemaining)
16111 {
16112     long nominalTickLength, nextTickLength;
16113
16114     if (timeRemaining > 0L && timeRemaining <= 10000L)
16115       nominalTickLength = 100L;
16116     else
16117       nominalTickLength = 1000L;
16118     nextTickLength = timeRemaining % nominalTickLength;
16119     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16120
16121     return nextTickLength;
16122 }
16123
16124 /* Adjust clock one minute up or down */
16125 void
16126 AdjustClock (Boolean which, int dir)
16127 {
16128     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16129     if(which) blackTimeRemaining += 60000*dir;
16130     else      whiteTimeRemaining += 60000*dir;
16131     DisplayBothClocks();
16132     adjustedClock = TRUE;
16133 }
16134
16135 /* Stop clocks and reset to a fresh time control */
16136 void
16137 ResetClocks ()
16138 {
16139     (void) StopClockTimer();
16140     if (appData.icsActive) {
16141         whiteTimeRemaining = blackTimeRemaining = 0;
16142     } else if (searchTime) {
16143         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16144         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16145     } else { /* [HGM] correct new time quote for time odds */
16146         whiteTC = blackTC = fullTimeControlString;
16147         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16148         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16149     }
16150     if (whiteFlag || blackFlag) {
16151         DisplayTitle("");
16152         whiteFlag = blackFlag = FALSE;
16153     }
16154     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16155     DisplayBothClocks();
16156     adjustedClock = FALSE;
16157 }
16158
16159 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16160
16161 /* Decrement running clock by amount of time that has passed */
16162 void
16163 DecrementClocks ()
16164 {
16165     long timeRemaining;
16166     long lastTickLength, fudge;
16167     TimeMark now;
16168
16169     if (!appData.clockMode) return;
16170     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16171
16172     GetTimeMark(&now);
16173
16174     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16175
16176     /* Fudge if we woke up a little too soon */
16177     fudge = intendedTickLength - lastTickLength;
16178     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16179
16180     if (WhiteOnMove(forwardMostMove)) {
16181         if(whiteNPS >= 0) lastTickLength = 0;
16182         timeRemaining = whiteTimeRemaining -= lastTickLength;
16183         if(timeRemaining < 0 && !appData.icsActive) {
16184             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16185             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16186                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16187                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16188             }
16189         }
16190         DisplayWhiteClock(whiteTimeRemaining - fudge,
16191                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16192     } else {
16193         if(blackNPS >= 0) lastTickLength = 0;
16194         timeRemaining = blackTimeRemaining -= lastTickLength;
16195         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16196             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16197             if(suddenDeath) {
16198                 blackStartMove = forwardMostMove;
16199                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16200             }
16201         }
16202         DisplayBlackClock(blackTimeRemaining - fudge,
16203                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16204     }
16205     if (CheckFlags()) return;
16206
16207     tickStartTM = now;
16208     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16209     StartClockTimer(intendedTickLength);
16210
16211     /* if the time remaining has fallen below the alarm threshold, sound the
16212      * alarm. if the alarm has sounded and (due to a takeback or time control
16213      * with increment) the time remaining has increased to a level above the
16214      * threshold, reset the alarm so it can sound again.
16215      */
16216
16217     if (appData.icsActive && appData.icsAlarm) {
16218
16219         /* make sure we are dealing with the user's clock */
16220         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16221                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16222            )) return;
16223
16224         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16225             alarmSounded = FALSE;
16226         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16227             PlayAlarmSound();
16228             alarmSounded = TRUE;
16229         }
16230     }
16231 }
16232
16233
16234 /* A player has just moved, so stop the previously running
16235    clock and (if in clock mode) start the other one.
16236    We redisplay both clocks in case we're in ICS mode, because
16237    ICS gives us an update to both clocks after every move.
16238    Note that this routine is called *after* forwardMostMove
16239    is updated, so the last fractional tick must be subtracted
16240    from the color that is *not* on move now.
16241 */
16242 void
16243 SwitchClocks (int newMoveNr)
16244 {
16245     long lastTickLength;
16246     TimeMark now;
16247     int flagged = FALSE;
16248
16249     GetTimeMark(&now);
16250
16251     if (StopClockTimer() && appData.clockMode) {
16252         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16253         if (!WhiteOnMove(forwardMostMove)) {
16254             if(blackNPS >= 0) lastTickLength = 0;
16255             blackTimeRemaining -= lastTickLength;
16256            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16257 //         if(pvInfoList[forwardMostMove].time == -1)
16258                  pvInfoList[forwardMostMove].time =               // use GUI time
16259                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16260         } else {
16261            if(whiteNPS >= 0) lastTickLength = 0;
16262            whiteTimeRemaining -= lastTickLength;
16263            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16264 //         if(pvInfoList[forwardMostMove].time == -1)
16265                  pvInfoList[forwardMostMove].time =
16266                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16267         }
16268         flagged = CheckFlags();
16269     }
16270     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16271     CheckTimeControl();
16272
16273     if (flagged || !appData.clockMode) return;
16274
16275     switch (gameMode) {
16276       case MachinePlaysBlack:
16277       case MachinePlaysWhite:
16278       case BeginningOfGame:
16279         if (pausing) return;
16280         break;
16281
16282       case EditGame:
16283       case PlayFromGameFile:
16284       case IcsExamining:
16285         return;
16286
16287       default:
16288         break;
16289     }
16290
16291     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16292         if(WhiteOnMove(forwardMostMove))
16293              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16294         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16295     }
16296
16297     tickStartTM = now;
16298     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16299       whiteTimeRemaining : blackTimeRemaining);
16300     StartClockTimer(intendedTickLength);
16301 }
16302
16303
16304 /* Stop both clocks */
16305 void
16306 StopClocks ()
16307 {
16308     long lastTickLength;
16309     TimeMark now;
16310
16311     if (!StopClockTimer()) return;
16312     if (!appData.clockMode) return;
16313
16314     GetTimeMark(&now);
16315
16316     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16317     if (WhiteOnMove(forwardMostMove)) {
16318         if(whiteNPS >= 0) lastTickLength = 0;
16319         whiteTimeRemaining -= lastTickLength;
16320         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16321     } else {
16322         if(blackNPS >= 0) lastTickLength = 0;
16323         blackTimeRemaining -= lastTickLength;
16324         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16325     }
16326     CheckFlags();
16327 }
16328
16329 /* Start clock of player on move.  Time may have been reset, so
16330    if clock is already running, stop and restart it. */
16331 void
16332 StartClocks ()
16333 {
16334     (void) StopClockTimer(); /* in case it was running already */
16335     DisplayBothClocks();
16336     if (CheckFlags()) return;
16337
16338     if (!appData.clockMode) return;
16339     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16340
16341     GetTimeMark(&tickStartTM);
16342     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16343       whiteTimeRemaining : blackTimeRemaining);
16344
16345    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16346     whiteNPS = blackNPS = -1;
16347     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16348        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16349         whiteNPS = first.nps;
16350     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16351        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16352         blackNPS = first.nps;
16353     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16354         whiteNPS = second.nps;
16355     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16356         blackNPS = second.nps;
16357     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16358
16359     StartClockTimer(intendedTickLength);
16360 }
16361
16362 char *
16363 TimeString (long ms)
16364 {
16365     long second, minute, hour, day;
16366     char *sign = "";
16367     static char buf[32];
16368
16369     if (ms > 0 && ms <= 9900) {
16370       /* convert milliseconds to tenths, rounding up */
16371       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16372
16373       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16374       return buf;
16375     }
16376
16377     /* convert milliseconds to seconds, rounding up */
16378     /* use floating point to avoid strangeness of integer division
16379        with negative dividends on many machines */
16380     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16381
16382     if (second < 0) {
16383         sign = "-";
16384         second = -second;
16385     }
16386
16387     day = second / (60 * 60 * 24);
16388     second = second % (60 * 60 * 24);
16389     hour = second / (60 * 60);
16390     second = second % (60 * 60);
16391     minute = second / 60;
16392     second = second % 60;
16393
16394     if (day > 0)
16395       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16396               sign, day, hour, minute, second);
16397     else if (hour > 0)
16398       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16399     else
16400       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16401
16402     return buf;
16403 }
16404
16405
16406 /*
16407  * This is necessary because some C libraries aren't ANSI C compliant yet.
16408  */
16409 char *
16410 StrStr (char *string, char *match)
16411 {
16412     int i, length;
16413
16414     length = strlen(match);
16415
16416     for (i = strlen(string) - length; i >= 0; i--, string++)
16417       if (!strncmp(match, string, length))
16418         return string;
16419
16420     return NULL;
16421 }
16422
16423 char *
16424 StrCaseStr (char *string, char *match)
16425 {
16426     int i, j, length;
16427
16428     length = strlen(match);
16429
16430     for (i = strlen(string) - length; i >= 0; i--, string++) {
16431         for (j = 0; j < length; j++) {
16432             if (ToLower(match[j]) != ToLower(string[j]))
16433               break;
16434         }
16435         if (j == length) return string;
16436     }
16437
16438     return NULL;
16439 }
16440
16441 #ifndef _amigados
16442 int
16443 StrCaseCmp (char *s1, char *s2)
16444 {
16445     char c1, c2;
16446
16447     for (;;) {
16448         c1 = ToLower(*s1++);
16449         c2 = ToLower(*s2++);
16450         if (c1 > c2) return 1;
16451         if (c1 < c2) return -1;
16452         if (c1 == NULLCHAR) return 0;
16453     }
16454 }
16455
16456
16457 int
16458 ToLower (int c)
16459 {
16460     return isupper(c) ? tolower(c) : c;
16461 }
16462
16463
16464 int
16465 ToUpper (int c)
16466 {
16467     return islower(c) ? toupper(c) : c;
16468 }
16469 #endif /* !_amigados    */
16470
16471 char *
16472 StrSave (char *s)
16473 {
16474   char *ret;
16475
16476   if ((ret = (char *) malloc(strlen(s) + 1)))
16477     {
16478       safeStrCpy(ret, s, strlen(s)+1);
16479     }
16480   return ret;
16481 }
16482
16483 char *
16484 StrSavePtr (char *s, char **savePtr)
16485 {
16486     if (*savePtr) {
16487         free(*savePtr);
16488     }
16489     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16490       safeStrCpy(*savePtr, s, strlen(s)+1);
16491     }
16492     return(*savePtr);
16493 }
16494
16495 char *
16496 PGNDate ()
16497 {
16498     time_t clock;
16499     struct tm *tm;
16500     char buf[MSG_SIZ];
16501
16502     clock = time((time_t *)NULL);
16503     tm = localtime(&clock);
16504     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16505             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16506     return StrSave(buf);
16507 }
16508
16509
16510 char *
16511 PositionToFEN (int move, char *overrideCastling)
16512 {
16513     int i, j, fromX, fromY, toX, toY;
16514     int whiteToPlay;
16515     char buf[MSG_SIZ];
16516     char *p, *q;
16517     int emptycount;
16518     ChessSquare piece;
16519
16520     whiteToPlay = (gameMode == EditPosition) ?
16521       !blackPlaysFirst : (move % 2 == 0);
16522     p = buf;
16523
16524     /* Piece placement data */
16525     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16526         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16527         emptycount = 0;
16528         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16529             if (boards[move][i][j] == EmptySquare) {
16530                 emptycount++;
16531             } else { ChessSquare piece = boards[move][i][j];
16532                 if (emptycount > 0) {
16533                     if(emptycount<10) /* [HGM] can be >= 10 */
16534                         *p++ = '0' + emptycount;
16535                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16536                     emptycount = 0;
16537                 }
16538                 if(PieceToChar(piece) == '+') {
16539                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16540                     *p++ = '+';
16541                     piece = (ChessSquare)(DEMOTED piece);
16542                 }
16543                 *p++ = PieceToChar(piece);
16544                 if(p[-1] == '~') {
16545                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16546                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16547                     *p++ = '~';
16548                 }
16549             }
16550         }
16551         if (emptycount > 0) {
16552             if(emptycount<10) /* [HGM] can be >= 10 */
16553                 *p++ = '0' + emptycount;
16554             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16555             emptycount = 0;
16556         }
16557         *p++ = '/';
16558     }
16559     *(p - 1) = ' ';
16560
16561     /* [HGM] print Crazyhouse or Shogi holdings */
16562     if( gameInfo.holdingsWidth ) {
16563         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16564         q = p;
16565         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16566             piece = boards[move][i][BOARD_WIDTH-1];
16567             if( piece != EmptySquare )
16568               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16569                   *p++ = PieceToChar(piece);
16570         }
16571         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16572             piece = boards[move][BOARD_HEIGHT-i-1][0];
16573             if( piece != EmptySquare )
16574               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16575                   *p++ = PieceToChar(piece);
16576         }
16577
16578         if( q == p ) *p++ = '-';
16579         *p++ = ']';
16580         *p++ = ' ';
16581     }
16582
16583     /* Active color */
16584     *p++ = whiteToPlay ? 'w' : 'b';
16585     *p++ = ' ';
16586
16587   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16588     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16589   } else {
16590   if(nrCastlingRights) {
16591      q = p;
16592      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16593        /* [HGM] write directly from rights */
16594            if(boards[move][CASTLING][2] != NoRights &&
16595               boards[move][CASTLING][0] != NoRights   )
16596                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16597            if(boards[move][CASTLING][2] != NoRights &&
16598               boards[move][CASTLING][1] != NoRights   )
16599                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16600            if(boards[move][CASTLING][5] != NoRights &&
16601               boards[move][CASTLING][3] != NoRights   )
16602                 *p++ = boards[move][CASTLING][3] + AAA;
16603            if(boards[move][CASTLING][5] != NoRights &&
16604               boards[move][CASTLING][4] != NoRights   )
16605                 *p++ = boards[move][CASTLING][4] + AAA;
16606      } else {
16607
16608         /* [HGM] write true castling rights */
16609         if( nrCastlingRights == 6 ) {
16610             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16611                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16612             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16613                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16614             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16615                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16616             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16617                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16618         }
16619      }
16620      if (q == p) *p++ = '-'; /* No castling rights */
16621      *p++ = ' ';
16622   }
16623
16624   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16625      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16626     /* En passant target square */
16627     if (move > backwardMostMove) {
16628         fromX = moveList[move - 1][0] - AAA;
16629         fromY = moveList[move - 1][1] - ONE;
16630         toX = moveList[move - 1][2] - AAA;
16631         toY = moveList[move - 1][3] - ONE;
16632         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16633             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16634             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16635             fromX == toX) {
16636             /* 2-square pawn move just happened */
16637             *p++ = toX + AAA;
16638             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16639         } else {
16640             *p++ = '-';
16641         }
16642     } else if(move == backwardMostMove) {
16643         // [HGM] perhaps we should always do it like this, and forget the above?
16644         if((signed char)boards[move][EP_STATUS] >= 0) {
16645             *p++ = boards[move][EP_STATUS] + AAA;
16646             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16647         } else {
16648             *p++ = '-';
16649         }
16650     } else {
16651         *p++ = '-';
16652     }
16653     *p++ = ' ';
16654   }
16655   }
16656
16657     /* [HGM] find reversible plies */
16658     {   int i = 0, j=move;
16659
16660         if (appData.debugMode) { int k;
16661             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16662             for(k=backwardMostMove; k<=forwardMostMove; k++)
16663                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16664
16665         }
16666
16667         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16668         if( j == backwardMostMove ) i += initialRulePlies;
16669         sprintf(p, "%d ", i);
16670         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16671     }
16672     /* Fullmove number */
16673     sprintf(p, "%d", (move / 2) + 1);
16674
16675     return StrSave(buf);
16676 }
16677
16678 Boolean
16679 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16680 {
16681     int i, j;
16682     char *p, c;
16683     int emptycount;
16684     ChessSquare piece;
16685
16686     p = fen;
16687
16688     /* [HGM] by default clear Crazyhouse holdings, if present */
16689     if(gameInfo.holdingsWidth) {
16690        for(i=0; i<BOARD_HEIGHT; i++) {
16691            board[i][0]             = EmptySquare; /* black holdings */
16692            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16693            board[i][1]             = (ChessSquare) 0; /* black counts */
16694            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16695        }
16696     }
16697
16698     /* Piece placement data */
16699     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16700         j = 0;
16701         for (;;) {
16702             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16703                 if (*p == '/') p++;
16704                 emptycount = gameInfo.boardWidth - j;
16705                 while (emptycount--)
16706                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16707                 break;
16708 #if(BOARD_FILES >= 10)
16709             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16710                 p++; emptycount=10;
16711                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16712                 while (emptycount--)
16713                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16714 #endif
16715             } else if (isdigit(*p)) {
16716                 emptycount = *p++ - '0';
16717                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16718                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16719                 while (emptycount--)
16720                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16721             } else if (*p == '+' || isalpha(*p)) {
16722                 if (j >= gameInfo.boardWidth) return FALSE;
16723                 if(*p=='+') {
16724                     piece = CharToPiece(*++p);
16725                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16726                     piece = (ChessSquare) (PROMOTED piece ); p++;
16727                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16728                 } else piece = CharToPiece(*p++);
16729
16730                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16731                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16732                     piece = (ChessSquare) (PROMOTED piece);
16733                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16734                     p++;
16735                 }
16736                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16737             } else {
16738                 return FALSE;
16739             }
16740         }
16741     }
16742     while (*p == '/' || *p == ' ') p++;
16743
16744     /* [HGM] look for Crazyhouse holdings here */
16745     while(*p==' ') p++;
16746     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16747         if(*p == '[') p++;
16748         if(*p == '-' ) p++; /* empty holdings */ else {
16749             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16750             /* if we would allow FEN reading to set board size, we would   */
16751             /* have to add holdings and shift the board read so far here   */
16752             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16753                 p++;
16754                 if((int) piece >= (int) BlackPawn ) {
16755                     i = (int)piece - (int)BlackPawn;
16756                     i = PieceToNumber((ChessSquare)i);
16757                     if( i >= gameInfo.holdingsSize ) return FALSE;
16758                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16759                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16760                 } else {
16761                     i = (int)piece - (int)WhitePawn;
16762                     i = PieceToNumber((ChessSquare)i);
16763                     if( i >= gameInfo.holdingsSize ) return FALSE;
16764                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16765                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16766                 }
16767             }
16768         }
16769         if(*p == ']') p++;
16770     }
16771
16772     while(*p == ' ') p++;
16773
16774     /* Active color */
16775     c = *p++;
16776     if(appData.colorNickNames) {
16777       if( c == appData.colorNickNames[0] ) c = 'w'; else
16778       if( c == appData.colorNickNames[1] ) c = 'b';
16779     }
16780     switch (c) {
16781       case 'w':
16782         *blackPlaysFirst = FALSE;
16783         break;
16784       case 'b':
16785         *blackPlaysFirst = TRUE;
16786         break;
16787       default:
16788         return FALSE;
16789     }
16790
16791     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16792     /* return the extra info in global variiables             */
16793
16794     /* set defaults in case FEN is incomplete */
16795     board[EP_STATUS] = EP_UNKNOWN;
16796     for(i=0; i<nrCastlingRights; i++ ) {
16797         board[CASTLING][i] =
16798             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16799     }   /* assume possible unless obviously impossible */
16800     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16801     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16802     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16803                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16804     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16805     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16806     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16807                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16808     FENrulePlies = 0;
16809
16810     while(*p==' ') p++;
16811     if(nrCastlingRights) {
16812       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16813           /* castling indicator present, so default becomes no castlings */
16814           for(i=0; i<nrCastlingRights; i++ ) {
16815                  board[CASTLING][i] = NoRights;
16816           }
16817       }
16818       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16819              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16820              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16821              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16822         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16823
16824         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16825             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16826             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16827         }
16828         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16829             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16830         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16831                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16832         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16833                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16834         switch(c) {
16835           case'K':
16836               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16837               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16838               board[CASTLING][2] = whiteKingFile;
16839               break;
16840           case'Q':
16841               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16842               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16843               board[CASTLING][2] = whiteKingFile;
16844               break;
16845           case'k':
16846               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16847               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16848               board[CASTLING][5] = blackKingFile;
16849               break;
16850           case'q':
16851               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16852               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16853               board[CASTLING][5] = blackKingFile;
16854           case '-':
16855               break;
16856           default: /* FRC castlings */
16857               if(c >= 'a') { /* black rights */
16858                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16859                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16860                   if(i == BOARD_RGHT) break;
16861                   board[CASTLING][5] = i;
16862                   c -= AAA;
16863                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16864                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16865                   if(c > i)
16866                       board[CASTLING][3] = c;
16867                   else
16868                       board[CASTLING][4] = c;
16869               } else { /* white rights */
16870                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16871                     if(board[0][i] == WhiteKing) break;
16872                   if(i == BOARD_RGHT) break;
16873                   board[CASTLING][2] = i;
16874                   c -= AAA - 'a' + 'A';
16875                   if(board[0][c] >= WhiteKing) break;
16876                   if(c > i)
16877                       board[CASTLING][0] = c;
16878                   else
16879                       board[CASTLING][1] = c;
16880               }
16881         }
16882       }
16883       for(i=0; i<nrCastlingRights; i++)
16884         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16885     if (appData.debugMode) {
16886         fprintf(debugFP, "FEN castling rights:");
16887         for(i=0; i<nrCastlingRights; i++)
16888         fprintf(debugFP, " %d", board[CASTLING][i]);
16889         fprintf(debugFP, "\n");
16890     }
16891
16892       while(*p==' ') p++;
16893     }
16894
16895     /* read e.p. field in games that know e.p. capture */
16896     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16897        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16898       if(*p=='-') {
16899         p++; board[EP_STATUS] = EP_NONE;
16900       } else {
16901          char c = *p++ - AAA;
16902
16903          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16904          if(*p >= '0' && *p <='9') p++;
16905          board[EP_STATUS] = c;
16906       }
16907     }
16908
16909
16910     if(sscanf(p, "%d", &i) == 1) {
16911         FENrulePlies = i; /* 50-move ply counter */
16912         /* (The move number is still ignored)    */
16913     }
16914
16915     return TRUE;
16916 }
16917
16918 void
16919 EditPositionPasteFEN (char *fen)
16920 {
16921   if (fen != NULL) {
16922     Board initial_position;
16923
16924     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16925       DisplayError(_("Bad FEN position in clipboard"), 0);
16926       return ;
16927     } else {
16928       int savedBlackPlaysFirst = blackPlaysFirst;
16929       EditPositionEvent();
16930       blackPlaysFirst = savedBlackPlaysFirst;
16931       CopyBoard(boards[0], initial_position);
16932       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16933       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16934       DisplayBothClocks();
16935       DrawPosition(FALSE, boards[currentMove]);
16936     }
16937   }
16938 }
16939
16940 static char cseq[12] = "\\   ";
16941
16942 Boolean
16943 set_cont_sequence (char *new_seq)
16944 {
16945     int len;
16946     Boolean ret;
16947
16948     // handle bad attempts to set the sequence
16949         if (!new_seq)
16950                 return 0; // acceptable error - no debug
16951
16952     len = strlen(new_seq);
16953     ret = (len > 0) && (len < sizeof(cseq));
16954     if (ret)
16955       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16956     else if (appData.debugMode)
16957       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16958     return ret;
16959 }
16960
16961 /*
16962     reformat a source message so words don't cross the width boundary.  internal
16963     newlines are not removed.  returns the wrapped size (no null character unless
16964     included in source message).  If dest is NULL, only calculate the size required
16965     for the dest buffer.  lp argument indicats line position upon entry, and it's
16966     passed back upon exit.
16967 */
16968 int
16969 wrap (char *dest, char *src, int count, int width, int *lp)
16970 {
16971     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16972
16973     cseq_len = strlen(cseq);
16974     old_line = line = *lp;
16975     ansi = len = clen = 0;
16976
16977     for (i=0; i < count; i++)
16978     {
16979         if (src[i] == '\033')
16980             ansi = 1;
16981
16982         // if we hit the width, back up
16983         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16984         {
16985             // store i & len in case the word is too long
16986             old_i = i, old_len = len;
16987
16988             // find the end of the last word
16989             while (i && src[i] != ' ' && src[i] != '\n')
16990             {
16991                 i--;
16992                 len--;
16993             }
16994
16995             // word too long?  restore i & len before splitting it
16996             if ((old_i-i+clen) >= width)
16997             {
16998                 i = old_i;
16999                 len = old_len;
17000             }
17001
17002             // extra space?
17003             if (i && src[i-1] == ' ')
17004                 len--;
17005
17006             if (src[i] != ' ' && src[i] != '\n')
17007             {
17008                 i--;
17009                 if (len)
17010                     len--;
17011             }
17012
17013             // now append the newline and continuation sequence
17014             if (dest)
17015                 dest[len] = '\n';
17016             len++;
17017             if (dest)
17018                 strncpy(dest+len, cseq, cseq_len);
17019             len += cseq_len;
17020             line = cseq_len;
17021             clen = cseq_len;
17022             continue;
17023         }
17024
17025         if (dest)
17026             dest[len] = src[i];
17027         len++;
17028         if (!ansi)
17029             line++;
17030         if (src[i] == '\n')
17031             line = 0;
17032         if (src[i] == 'm')
17033             ansi = 0;
17034     }
17035     if (dest && appData.debugMode)
17036     {
17037         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17038             count, width, line, len, *lp);
17039         show_bytes(debugFP, src, count);
17040         fprintf(debugFP, "\ndest: ");
17041         show_bytes(debugFP, dest, len);
17042         fprintf(debugFP, "\n");
17043     }
17044     *lp = dest ? line : old_line;
17045
17046     return len;
17047 }
17048
17049 // [HGM] vari: routines for shelving variations
17050 Boolean modeRestore = FALSE;
17051
17052 void
17053 PushInner (int firstMove, int lastMove)
17054 {
17055         int i, j, nrMoves = lastMove - firstMove;
17056
17057         // push current tail of game on stack
17058         savedResult[storedGames] = gameInfo.result;
17059         savedDetails[storedGames] = gameInfo.resultDetails;
17060         gameInfo.resultDetails = NULL;
17061         savedFirst[storedGames] = firstMove;
17062         savedLast [storedGames] = lastMove;
17063         savedFramePtr[storedGames] = framePtr;
17064         framePtr -= nrMoves; // reserve space for the boards
17065         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17066             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17067             for(j=0; j<MOVE_LEN; j++)
17068                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17069             for(j=0; j<2*MOVE_LEN; j++)
17070                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17071             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17072             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17073             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17074             pvInfoList[firstMove+i-1].depth = 0;
17075             commentList[framePtr+i] = commentList[firstMove+i];
17076             commentList[firstMove+i] = NULL;
17077         }
17078
17079         storedGames++;
17080         forwardMostMove = firstMove; // truncate game so we can start variation
17081 }
17082
17083 void
17084 PushTail (int firstMove, int lastMove)
17085 {
17086         if(appData.icsActive) { // only in local mode
17087                 forwardMostMove = currentMove; // mimic old ICS behavior
17088                 return;
17089         }
17090         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17091
17092         PushInner(firstMove, lastMove);
17093         if(storedGames == 1) GreyRevert(FALSE);
17094         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17095 }
17096
17097 void
17098 PopInner (Boolean annotate)
17099 {
17100         int i, j, nrMoves;
17101         char buf[8000], moveBuf[20];
17102
17103         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17104         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17105         nrMoves = savedLast[storedGames] - currentMove;
17106         if(annotate) {
17107                 int cnt = 10;
17108                 if(!WhiteOnMove(currentMove))
17109                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17110                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17111                 for(i=currentMove; i<forwardMostMove; i++) {
17112                         if(WhiteOnMove(i))
17113                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17114                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17115                         strcat(buf, moveBuf);
17116                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17117                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17118                 }
17119                 strcat(buf, ")");
17120         }
17121         for(i=1; i<=nrMoves; i++) { // copy last variation back
17122             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17123             for(j=0; j<MOVE_LEN; j++)
17124                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17125             for(j=0; j<2*MOVE_LEN; j++)
17126                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17127             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17128             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17129             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17130             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17131             commentList[currentMove+i] = commentList[framePtr+i];
17132             commentList[framePtr+i] = NULL;
17133         }
17134         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17135         framePtr = savedFramePtr[storedGames];
17136         gameInfo.result = savedResult[storedGames];
17137         if(gameInfo.resultDetails != NULL) {
17138             free(gameInfo.resultDetails);
17139       }
17140         gameInfo.resultDetails = savedDetails[storedGames];
17141         forwardMostMove = currentMove + nrMoves;
17142 }
17143
17144 Boolean
17145 PopTail (Boolean annotate)
17146 {
17147         if(appData.icsActive) return FALSE; // only in local mode
17148         if(!storedGames) return FALSE; // sanity
17149         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17150
17151         PopInner(annotate);
17152         if(currentMove < forwardMostMove) ForwardEvent(); else
17153         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17154
17155         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17156         return TRUE;
17157 }
17158
17159 void
17160 CleanupTail ()
17161 {       // remove all shelved variations
17162         int i;
17163         for(i=0; i<storedGames; i++) {
17164             if(savedDetails[i])
17165                 free(savedDetails[i]);
17166             savedDetails[i] = NULL;
17167         }
17168         for(i=framePtr; i<MAX_MOVES; i++) {
17169                 if(commentList[i]) free(commentList[i]);
17170                 commentList[i] = NULL;
17171         }
17172         framePtr = MAX_MOVES-1;
17173         storedGames = 0;
17174 }
17175
17176 void
17177 LoadVariation (int index, char *text)
17178 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17179         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17180         int level = 0, move;
17181
17182         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17183         // first find outermost bracketing variation
17184         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17185             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17186                 if(*p == '{') wait = '}'; else
17187                 if(*p == '[') wait = ']'; else
17188                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17189                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17190             }
17191             if(*p == wait) wait = NULLCHAR; // closing ]} found
17192             p++;
17193         }
17194         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17195         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17196         end[1] = NULLCHAR; // clip off comment beyond variation
17197         ToNrEvent(currentMove-1);
17198         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17199         // kludge: use ParsePV() to append variation to game
17200         move = currentMove;
17201         ParsePV(start, TRUE, TRUE);
17202         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17203         ClearPremoveHighlights();
17204         CommentPopDown();
17205         ToNrEvent(currentMove+1);
17206 }
17207